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 "pbuilder_pbx.h"
30 #include "option.h"
31 #include "meta.h"
32 #include <qdir.h>
33 #include <qregexp.h>
34 #include <qcryptographichash.h>
35 #include <qdebug.h>
36 #include <qsettings.h>
37 #include <qstring.h>
38 #include <stdlib.h>
39 #include <time.h>
40 #ifdef Q_OS_UNIX
41 #  include <sys/types.h>
42 #  include <sys/stat.h>
43 #endif
44 #ifdef Q_OS_DARWIN
45 #include <ApplicationServices/ApplicationServices.h>
46 #include <private/qcore_mac_p.h>
47 #endif
48 
49 QT_BEGIN_NAMESPACE
50 
51 //#define GENERATE_AGGREGRATE_SUBDIR
52 
53 // Note: this is fairly hacky, but it does the job...
54 
55 using namespace QMakeInternal;
56 
qtSha1(const QByteArray & src)57 static QString qtSha1(const QByteArray &src)
58 {
59     QByteArray digest = QCryptographicHash::hash(src, QCryptographicHash::Sha1);
60     return QString::fromLatin1(digest.toHex());
61 }
62 
63 bool
writeMakefile(QTextStream & t)64 ProjectBuilderMakefileGenerator::writeMakefile(QTextStream &t)
65 {
66     writingUnixMakefileGenerator = false;
67     if(!project->values("QMAKE_FAILED_REQUIREMENTS").isEmpty()) {
68         /* for now just dump, I need to generated an empty xml or something.. */
69         fprintf(stderr, "Project file not generated because all requirements not met:\n\t%s\n",
70                 var("QMAKE_FAILED_REQUIREMENTS").toLatin1().constData());
71         return true;
72     }
73 
74     project->values("MAKEFILE").clear();
75     project->values("MAKEFILE").append("Makefile");
76     if(project->first("TEMPLATE") == "app" || project->first("TEMPLATE") == "lib")
77         return writeMakeParts(t);
78     else if(project->first("TEMPLATE") == "subdirs")
79         return writeSubDirs(t);
80     return false;
81 }
82 
83 struct ProjectBuilderSubDirs {
84     QMakeProject *project;
85     QString subdir;
86     bool autoDelete;
ProjectBuilderSubDirsProjectBuilderSubDirs87     ProjectBuilderSubDirs(QMakeProject *p, QString s, bool a=true) : project(p), subdir(s), autoDelete(a) { }
~ProjectBuilderSubDirsProjectBuilderSubDirs88     ~ProjectBuilderSubDirs() {
89         if(autoDelete)
90             delete project;
91     }
92 };
93 
94 bool
writeSubDirs(QTextStream & t)95 ProjectBuilderMakefileGenerator::writeSubDirs(QTextStream &t)
96 {
97     //HEADER
98     const int pbVersion = pbuilderVersion();
99     t << "// !$*UTF8*$!\n"
100       << "{\n"
101       << "\t" << writeSettings("archiveVersion", "1", SettingsNoQuote) << ";\n"
102       << "\tclasses = {\n\t};\n"
103       << "\t" << writeSettings("objectVersion", QString::number(pbVersion), SettingsNoQuote) << ";\n"
104       << "\tobjects = {\n";
105 
106     //SUBDIRS
107     QList<ProjectBuilderSubDirs*> pb_subdirs;
108     pb_subdirs.append(new ProjectBuilderSubDirs(project, QString(), false));
109     QString oldpwd = qmake_getpwd();
110     QString oldoutpwd = Option::output_dir;
111     QMap<QString, ProStringList> groups;
112     for(int pb_subdir = 0; pb_subdir < pb_subdirs.size(); ++pb_subdir) {
113         ProjectBuilderSubDirs *pb = pb_subdirs[pb_subdir];
114         const ProStringList &subdirs = pb->project->values("SUBDIRS");
115         for(int subdir = 0; subdir < subdirs.count(); subdir++) {
116             ProString tmpk = subdirs[subdir];
117             const ProKey fkey(tmpk + ".file");
118             if (!pb->project->isEmpty(fkey)) {
119                 tmpk = pb->project->first(fkey);
120             } else {
121                 const ProKey skey(tmpk + ".subdir");
122                 if (!pb->project->isEmpty(skey))
123                     tmpk = pb->project->first(skey);
124             }
125             QString tmp = tmpk.toQString();
126             if(fileInfo(tmp).isRelative() && !pb->subdir.isEmpty()) {
127                 QString subdir = pb->subdir;
128                 if(!subdir.endsWith(Option::dir_sep))
129                     subdir += Option::dir_sep;
130                 tmp = subdir + tmp;
131             }
132             QFileInfo fi(fileInfo(Option::normalizePath(tmp)));
133             if(fi.exists()) {
134                 if(fi.isDir()) {
135                     QString profile = tmp;
136                     if(!profile.endsWith(Option::dir_sep))
137                         profile += Option::dir_sep;
138                     profile += fi.baseName() + Option::pro_ext;
139                     fi = QFileInfo(profile);
140                 }
141                 QMakeProject tmp_proj;
142                 QString dir = fi.path(), fn = fi.fileName();
143                 if(!dir.isEmpty()) {
144                     if(!qmake_setpwd(dir))
145                         fprintf(stderr, "Cannot find directory: %s\n", dir.toLatin1().constData());
146                 }
147                 Option::output_dir = Option::globals->shadowedPath(qmake_getpwd());
148                 if(tmp_proj.read(fn)) {
149                     if(tmp_proj.first("TEMPLATE") == "subdirs") {
150                         QMakeProject *pp = new QMakeProject(&tmp_proj);
151                         pb_subdirs += new ProjectBuilderSubDirs(pp, dir);
152                     } else if(tmp_proj.first("TEMPLATE") == "app" || tmp_proj.first("TEMPLATE") == "lib") {
153                         QString pbxproj = Option::output_dir + Option::dir_sep + tmp_proj.first("TARGET") + projectSuffix();
154                         if(!exists(pbxproj)) {
155                             warn_msg(WarnLogic, "Ignored (not found) '%s'", pbxproj.toLatin1().constData());
156                             goto nextfile; // # Dirty!
157                         }
158                         const QString project_key = keyFor(pbxproj + "_PROJECTREF");
159                         project->values("QMAKE_PBX_SUBDIRS") += pbxproj;
160                         //PROJECTREF
161                         {
162                             bool in_root = true;
163                             QString name = qmake_getpwd();
164                             if(project->isActiveConfig("flat")) {
165                                 QString flat_file = fileFixify(name, FileFixifyBackwards | FileFixifyRelative);
166                                 if(flat_file.indexOf(Option::dir_sep) != -1) {
167                                     QStringList dirs = flat_file.split(Option::dir_sep);
168                                     name = dirs.back();
169                                 }
170                             } else {
171                                 QString flat_file = fileFixify(name, FileFixifyBackwards | FileFixifyRelative);
172                                 if(QDir::isRelativePath(flat_file) && flat_file.indexOf(Option::dir_sep) != -1) {
173                                     QString last_grp("QMAKE_SUBDIR_PBX_HEIR_GROUP");
174                                     QStringList dirs = flat_file.split(Option::dir_sep);
175                                     name = dirs.back();
176                                     for(QStringList::Iterator dir_it = dirs.begin(); dir_it != dirs.end(); ++dir_it) {
177                                         QString new_grp(last_grp + Option::dir_sep + (*dir_it)), new_grp_key(keyFor(new_grp));
178                                         if(dir_it == dirs.begin()) {
179                                             if(!groups.contains(new_grp))
180                                                 project->values("QMAKE_SUBDIR_PBX_GROUPS").append(new_grp_key);
181                                         } else {
182                                             if(!groups[last_grp].contains(new_grp_key))
183                                                 groups[last_grp] += new_grp_key;
184                                         }
185                                         last_grp = new_grp;
186                                     }
187                                     groups[last_grp] += project_key;
188                                     in_root = false;
189                                 }
190                             }
191                             if(in_root)
192                                 project->values("QMAKE_SUBDIR_PBX_GROUPS") += project_key;
193                             t << "\t\t" << project_key << " = {\n"
194                               << "\t\t\t" << writeSettings("isa", "PBXFileReference", SettingsNoQuote) << ";\n"
195                               << "\t\t\t" << writeSettings("lastKnownFileType", "wrapper.pb-project") << ";\n"
196                               << "\t\t\t" << writeSettings("name", tmp_proj.first("TARGET") + projectSuffix()) << ";\n"
197                               << "\t\t\t" << writeSettings("path", pbxproj) << ";\n"
198                               << "\t\t\t" << writeSettings("sourceTree", "<absolute>") << ";\n"
199                               << "\t\t};\n";
200                             //WRAPPER
201                             t << "\t\t" << keyFor(pbxproj + "_WRAPPER") << " = {\n"
202                               << "\t\t\t" << writeSettings("isa", "PBXReferenceProxy", SettingsNoQuote) << ";\n";
203                             if(tmp_proj.first("TEMPLATE") == "app") {
204                                 t << "\t\t\t" << writeSettings("fileType", "wrapper.application") << ";\n"
205                                   << "\t\t\t" << writeSettings("path", tmp_proj.first("TARGET") + ".app") << ";\n";
206                             } else {
207                                 t << "\t\t\t" << writeSettings("fileType", "compiled.mach-o.dylib") << ";\n"
208                                   << "\t\t\t" << writeSettings("path", tmp_proj.first("TARGET") + ".dylib") << ";\n";
209                             }
210                             t << "\t\t\t" << writeSettings("remoteRef", keyFor(pbxproj + "_WRAPPERREF")) << ";\n"
211                               << "\t\t\t" << writeSettings("sourceTree", "BUILT_PRODUCTS_DIR", SettingsNoQuote) << ";\n"
212                               << "\t\t};\n";
213                             t << "\t\t" << keyFor(pbxproj + "_WRAPPERREF") << " = {\n"
214                               << "\t\t\t" << writeSettings("containerPortal", project_key) << ";\n"
215                               << "\t\t\t" << writeSettings("isa", "PBXContainerItemProxy", SettingsNoQuote) << ";\n"
216                               << "\t\t\t" << writeSettings("proxyType", "2") << ";\n"
217 //                              << "\t\t\t" << writeSettings("remoteGlobalIDString", keyFor(pbxproj + "QMAKE_PBX_REFERENCE")) << ";\n"
218                               << "\t\t\t" << writeSettings("remoteGlobalIDString", keyFor(pbxproj + "QMAKE_PBX_REFERENCE!!!")) << ";\n"
219                               << "\t\t\t" << writeSettings("remoteInfo", tmp_proj.first("TARGET")) << ";\n"
220                               << "\t\t};\n";
221                             //PRODUCTGROUP
222                             t << "\t\t" << keyFor(pbxproj + "_PRODUCTGROUP") << " = {\n"
223                               << "\t\t\t" << writeSettings("children", project->values(ProKey(pbxproj + "_WRAPPER")), SettingsAsList, 4) << ";\n"
224                               << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
225                               << "\t\t\t" << writeSettings("name", "Products") << ";\n"
226                               << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
227                               << "\t\t};\n";
228                         }
229 #ifdef GENERATE_AGGREGRATE_SUBDIR
230                         //TARGET (for aggregate)
231                         {
232                             //container
233                             const QString container_proxy = keyFor(pbxproj + "_CONTAINERPROXY");
234                             t << "\t\t" << container_proxy << " = {\n"
235                               << "\t\t\t" << writeSettings("containerPortal", project_key) << ";\n"
236                               << "\t\t\t" << writeSettings("isa", "PBXContainerItemProxy", SettingsNoQuote) << ";\n"
237                               << "\t\t\t" << writeSettings("proxyType", "1") << ";\n"
238                               << "\t\t\t" << writeSettings("remoteGlobalIDString", keyFor(pbxproj + "QMAKE_PBX_TARGET")) << ";\n"
239                               << "\t\t\t" << writeSettings("remoteInfo", tmp_proj.first("TARGET")) << ";\n"
240                               << "\t\t};\n";
241                             //targetref
242                             t << "\t\t" << keyFor(pbxproj + "_TARGETREF") << " = {\n"
243                               << "\t\t\t" << writeSettings("isa", "PBXTargetDependency", SettingsNoQuote) << ";\n"
244                               << "\t\t\t" << writeSettings("name", fixForOutput(tmp_proj.first("TARGET") +" (from " + tmp_proj.first("TARGET") + projectSuffix() + ")")) << ";\n"
245                               << "\t\t\t" << writeSettings("targetProxy", container_proxy) << ";\n"
246                               << "\t\t};\n";
247                         }
248 #endif
249                     }
250                 }
251             nextfile:
252                 qmake_setpwd(oldpwd);
253                 Option::output_dir = oldoutpwd;
254             }
255         }
256     }
257     qDeleteAll(pb_subdirs);
258     pb_subdirs.clear();
259 
260     for (QMap<QString, ProStringList>::Iterator grp_it = groups.begin(); grp_it != groups.end(); ++grp_it) {
261         t << "\t\t" << keyFor(grp_it.key()) << " = {\n"
262           << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
263           << "\t\t\t" << writeSettings("children", grp_it.value(), SettingsAsList, 4) << ";\n"
264           << "\t\t\t" << writeSettings("name", grp_it.key().section(Option::dir_sep, -1)) << ";\n"
265           << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
266           << "\t\t};\n";
267     }
268 
269     //DUMP EVERYTHING THAT TIES THE ABOVE TOGETHER
270     //BUILDCONFIGURATIONS
271     QString defaultConfig;
272     for(int as_release = 0; as_release < 2; as_release++)
273     {
274         QString configName = (as_release ? "Release" : "Debug");
275 
276         QMap<QString, QString> settings;
277         if(project->isActiveConfig("sdk") && !project->isEmpty("QMAKE_MAC_SDK"))
278             settings.insert("SDKROOT", project->first("QMAKE_MAC_SDK").toQString());
279         {
280             const ProStringList &l = project->values("QMAKE_MAC_XCODE_SETTINGS");
281             for(int i = 0; i < l.size(); ++i) {
282                 ProString name = l.at(i);
283                 const ProKey buildKey(name + ".build");
284                 if (!project->isEmpty(buildKey)) {
285                     const QString build = project->first(buildKey).toQString();
286                     if (build.toLower() != configName.toLower())
287                         continue;
288                 }
289                 const ProKey nkey(name + ".name");
290                 if (!project->isEmpty(nkey))
291                     name = project->first(nkey);
292                 const QString value = project->values(ProKey(name + ".value")).join(QString(Option::field_sep));
293                 settings.insert(name.toQString(), value);
294             }
295         }
296 
297         if (project->isActiveConfig("debug") != (bool)as_release)
298             defaultConfig = configName;
299         QString key = keyFor("QMAKE_SUBDIR_PBX_BUILDCONFIG_" + configName);
300         project->values("QMAKE_SUBDIR_PBX_BUILDCONFIGS").append(key);
301         t << "\t\t" << key << " = {\n"
302         << "\t\t\t" << writeSettings("isa", "XCBuildConfiguration", SettingsNoQuote) << ";\n"
303         << "\t\t\tbuildSettings = {\n";
304         for (QMap<QString, QString>::Iterator set_it = settings.begin(); set_it != settings.end(); ++set_it)
305             t << "\t\t\t\t" << writeSettings(set_it.key(), set_it.value()) << ";\n";
306         t << "\t\t\t};\n"
307           << "\t\t\t" << writeSettings("name", configName) << ";\n"
308           << "\t\t};\n";
309     }
310     t << "\t\t" << keyFor("QMAKE_SUBDIR_PBX_BUILDCONFIG_LIST") << " = {\n"
311       << "\t\t\t" << writeSettings("isa", "XCConfigurationList", SettingsNoQuote) << ";\n"
312       << "\t\t\t" << writeSettings("buildConfigurations", project->values("QMAKE_SUBDIR_PBX_BUILDCONFIGS"), SettingsAsList, 4) << ";\n"
313       << "\t\t\t" << writeSettings("defaultConfigurationIsVisible", "0", SettingsNoQuote) << ";\n"
314       << "\t\t\t" << writeSettings("defaultConfigurationName", defaultConfig, SettingsNoQuote) << ";\n"
315       << "\t\t};\n";
316 
317 #ifdef GENERATE_AGGREGRATE_SUBDIR
318     //target
319     t << "\t\t" << keyFor("QMAKE_SUBDIR_PBX_AGGREGATE_TARGET") << " = {\n"
320       << "\t\t\t" << writeSettings("buildPhases", ProStringList(), SettingsAsList, 4) << ";\n"
321       << "\t\t\tbuildSettings = {\n"
322       << "\t\t\t\t" << writeSettings("PRODUCT_NAME", project->first("TARGET")) << ";\n"
323       << "\t\t\t};\n";
324     {
325         ProStringList dependencies;
326         const ProStringList &qmake_subdirs = project->values("QMAKE_PBX_SUBDIRS");
327         for(int i = 0; i < qmake_subdirs.count(); i++)
328             dependencies += keyFor(qmake_subdirs[i] + "_TARGETREF");
329         t << "\t\t\t" << writeSettings("dependencies", dependencies, SettingsAsList, 4) << ";\n"
330     }
331     t << "\t\t\t" << writeSettings("isa", "PBXAggregateTarget", SettingsNoQuote) << ";\n"
332       << "\t\t\t" << writeSettings("name", project->first("TARGET")) << ";\n"
333       << "\t\t\t" << writeSettings("productName", project->first("TARGET")) << ";\n"
334       << "\t\t};\n";
335 #endif
336 
337     //ROOT_GROUP
338     t << "\t\t" << keyFor("QMAKE_SUBDIR_PBX_ROOT_GROUP") << " = {\n"
339       << "\t\t\t" << writeSettings("children", project->values("QMAKE_SUBDIR_PBX_GROUPS"), SettingsAsList, 4) << ";\n"
340       << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
341       << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
342       << "\t\t};\n";
343 
344 
345     //ROOT
346     t << "\t\t" << keyFor("QMAKE_SUBDIR_PBX_ROOT") << " = {\n"
347       << "\t\t\tbuildSettings = {\n"
348       << "\t\t\t};\n"
349       << "\t\t\t" << writeSettings("buildStyles", project->values("QMAKE_SUBDIR_PBX_BUILDSTYLES"), SettingsAsList, 4) << ";\n"
350       << "\t\t\t" << writeSettings("isa", "PBXProject", SettingsNoQuote) << ";\n"
351       << "\t\t\t" << writeSettings("mainGroup", keyFor("QMAKE_SUBDIR_PBX_ROOT_GROUP")) << ";\n"
352       << "\t\t\t" << writeSettings("projectDirPath", ProStringList()) << ";\n";
353     t << "\t\t\t" << writeSettings("buildConfigurationList", keyFor("QMAKE_SUBDIR_PBX_BUILDCONFIG_LIST")) << ";\n";
354     t << "\t\t\tprojectReferences = (\n";
355     {
356         const ProStringList &qmake_subdirs = project->values("QMAKE_PBX_SUBDIRS");
357         for(int i = 0; i < qmake_subdirs.count(); i++) {
358             const ProString &subdir = qmake_subdirs[i];
359             t << "\t\t\t\t{\n"
360               << "\t\t\t\t\t" << writeSettings("ProductGroup", keyFor(subdir + "_PRODUCTGROUP")) << ";\n"
361               << "\t\t\t\t\t" << writeSettings("ProjectRef", keyFor(subdir + "_PROJECTREF")) << ";\n"
362               << "\t\t\t\t},\n";
363         }
364     }
365     t << "\t\t\t);\n"
366       << "\t\t\t" << writeSettings("targets",
367 #ifdef GENERATE_AGGREGRATE_SUBDIR
368                                  project->values("QMAKE_SUBDIR_AGGREGATE_TARGET"),
369 #else
370                                  ProStringList(),
371 #endif
372                                    SettingsAsList, 4) << ";\n"
373       << "\t\t};\n";
374 
375     //FOOTER
376     t << "\t};\n"
377       << "\t" << writeSettings("rootObject", keyFor("QMAKE_SUBDIR_PBX_ROOT")) << ";\n"
378       << "}\n";
379 
380     return true;
381 }
382 
383 class ProjectBuilderSources
384 {
385     bool buildable, object_output;
386     QString key, group, compiler;
387 public:
388     ProjectBuilderSources(const QString &key, bool buildable = false, const QString &compiler = QString(), bool producesObject = false);
389     QStringList files(QMakeProject *project) const;
isBuildable() const390     inline bool isBuildable() const { return buildable; }
keyName() const391     inline QString keyName() const { return key; }
groupName() const392     inline QString groupName() const { return group; }
compilerName() const393     inline QString compilerName() const { return compiler; }
isObjectOutput(const QString & file) const394     inline bool isObjectOutput(const QString &file) const {
395         if (object_output)
396             return true;
397 
398         if (file.endsWith(Option::objc_ext))
399             return true;
400         if (file.endsWith(Option::objcpp_ext))
401             return true;
402 
403         for (int i = 0; i < Option::c_ext.size(); ++i) {
404             if (file.endsWith(Option::c_ext.at(i)))
405                 return true;
406         }
407         for (int i = 0; i < Option::cpp_ext.size(); ++i) {
408             if (file.endsWith(Option::cpp_ext.at(i)))
409                 return true;
410         }
411 
412         return false;
413     }
414 };
415 
ProjectBuilderSources(const QString & k,bool b,const QString & c,bool o)416 ProjectBuilderSources::ProjectBuilderSources(const QString &k, bool b, const QString &c, bool o) :
417     buildable(b), object_output(o), key(k), compiler(c)
418 {
419     // Override group name for a few common keys
420     if (k == "SOURCES" || k == "OBJECTIVE_SOURCES" || k == "HEADERS")
421         group = "Sources";
422     else if (k == "QMAKE_INTERNAL_INCLUDED_FILES")
423         group = "Supporting Files";
424     else if (k == "GENERATED_SOURCES" || k == "GENERATED_FILES")
425         group = "Generated Sources";
426     else if (k == "RESOURCES")
427         group = "Resources";
428     else if (group.isNull())
429         group = QString("Sources [") + c + "]";
430 }
431 
432 QStringList
files(QMakeProject * project) const433 ProjectBuilderSources::files(QMakeProject *project) const
434 {
435     QStringList ret = project->values(ProKey(key)).toQStringList();
436     if(key == "QMAKE_INTERNAL_INCLUDED_FILES") {
437         QString qtPrefix(project->propertyValue(ProKey("QT_INSTALL_PREFIX/get")).toQString() + '/');
438         QString qtSrcPrefix(project->propertyValue(ProKey("QT_INSTALL_PREFIX/src")).toQString() + '/');
439 
440         QStringList newret;
441         for(int i = 0; i < ret.size(); ++i) {
442             // Don't show files "internal" to Qt in Xcode
443             if (ret.at(i).startsWith(qtPrefix) || ret.at(i).startsWith(qtSrcPrefix))
444                 continue;
445 
446             newret.append(ret.at(i));
447         }
448         ret = newret;
449     }
450     if(key == "SOURCES" && project->first("TEMPLATE") == "app" && !project->isEmpty("ICON"))
451         ret.append(project->first("ICON").toQString());
452     return ret;
453 }
454 
xcodeFiletypeForFilename(const QString & filename)455 static QString xcodeFiletypeForFilename(const QString &filename)
456 {
457     for (const QString &ext : qAsConst(Option::cpp_ext)) {
458         if (filename.endsWith(ext))
459             return QStringLiteral("sourcecode.cpp.cpp");
460     }
461 
462     for (const QString &ext : qAsConst(Option::c_ext)) {
463         if (filename.endsWith(ext))
464             return QStringLiteral("sourcecode.c.c");
465     }
466 
467     for (const QString &ext : qAsConst(Option::h_ext)) {
468         if (filename.endsWith(ext))
469             return "sourcecode.c.h";
470     }
471 
472     if (filename.endsWith(Option::objcpp_ext))
473         return QStringLiteral("sourcecode.cpp.objcpp");
474     if (filename.endsWith(Option::objc_ext))
475         return QStringLiteral("sourcecode.c.objc");
476     if (filename.endsWith(QLatin1String(".framework")))
477         return QStringLiteral("wrapper.framework");
478     if (filename.endsWith(QLatin1String(".a")))
479         return QStringLiteral("archive.ar");
480     if (filename.endsWith(QLatin1String(".pro")) || filename.endsWith(QLatin1String(".qrc")))
481         return QStringLiteral("text");
482 
483     return QString();
484 }
485 
compareProvisioningTeams(const QVariantMap & a,const QVariantMap & b)486 static bool compareProvisioningTeams(const QVariantMap &a, const QVariantMap &b)
487 {
488     int aFree = a.value(QLatin1String("isFreeProvisioningTeam")).toBool() ? 1 : 0;
489     int bFree = b.value(QLatin1String("isFreeProvisioningTeam")).toBool() ? 1 : 0;
490     return aFree < bFree;
491 }
492 
provisioningTeams()493 static QList<QVariantMap> provisioningTeams()
494 {
495     const QSettings xcodeSettings(
496         QDir::homePath() + QLatin1String("/Library/Preferences/com.apple.dt.Xcode.plist"),
497         QSettings::NativeFormat);
498     const QVariantMap teamMap = xcodeSettings.value(QLatin1String("IDEProvisioningTeams")).toMap();
499     QList<QVariantMap> flatTeams;
500     for (QVariantMap::const_iterator it = teamMap.begin(), end = teamMap.end(); it != end; ++it) {
501         const QString emailAddress = it.key();
502         QVariantMap team = it.value().toMap();
503         team[QLatin1String("emailAddress")] = emailAddress;
504         flatTeams.append(team);
505     }
506 
507     // Sort teams so that Free Provisioning teams come last
508     std::sort(flatTeams.begin(), flatTeams.end(), ::compareProvisioningTeams);
509     return flatTeams;
510 }
511 
replaceLibrarySuffix(const QString & lib_file,const ProString & opt,QString & name,QString & library)512 bool ProjectBuilderMakefileGenerator::replaceLibrarySuffix(const QString &lib_file,
513                                                            const ProString &opt,
514                                                            QString &name, QString &library)
515 {
516     /* This isn't real nice, but it is real useful. This looks in a prl
517        for what the library will ultimately be called so we can stick it
518        in the ProjectFile. If the prl format ever changes (not likely) then
519        this will not really work. However, more concerning is that it will
520        encode the version number in the Project file which might be a bad
521        things in days to come? --Sam
522     */
523     if (lib_file.isEmpty())
524         return false;
525 
526     QMakeMetaInfo libinfo;
527     if (!libinfo.readLib(lib_file) || libinfo.isEmpty("QMAKE_PRL_TARGET"))
528         return false;
529 
530     const QString libDir = fileInfo(lib_file).absolutePath();
531     library = libDir + Option::dir_sep + libinfo.first("QMAKE_PRL_TARGET");
532 
533     debug_msg(1, "pbuilder: Found library (%s) via PRL %s (%s)",
534               opt.toLatin1().constData(), lib_file.toLatin1().constData(), library.toLatin1().constData());
535 
536     if (project->isActiveConfig("xcode_dynamic_library_suffix")) {
537         QString suffixSetting = project->first("QMAKE_XCODE_LIBRARY_SUFFIX_SETTING").toQString();
538         if (!suffixSetting.isEmpty()) {
539             QString librarySuffix = project->first("QMAKE_XCODE_LIBRARY_SUFFIX").toQString();
540             suffixSetting = "$(" + suffixSetting + ")";
541             if (!librarySuffix.isEmpty()) {
542                 int pos = library.lastIndexOf(librarySuffix + '.');
543                 if (pos == -1) {
544                     warn_msg(WarnLogic, "Failed to find expected suffix '%s' for library '%s'.",
545                              qPrintable(librarySuffix), qPrintable(library));
546                 } else {
547                     library.replace(pos, librarySuffix.length(), suffixSetting);
548                     if (name.endsWith(librarySuffix))
549                         name.chop(librarySuffix.length());
550                 }
551             } else {
552                 int pos = library.lastIndexOf(name);
553                 if (pos != -1)
554                     library.insert(pos + name.length(), suffixSetting);
555             }
556         }
557     }
558 
559     return true;
560 }
561 
562 bool
writeMakeParts(QTextStream & t)563 ProjectBuilderMakefileGenerator::writeMakeParts(QTextStream &t)
564 {
565     ProStringList tmp;
566 
567     //HEADER
568     const int pbVersion = pbuilderVersion();
569     ProStringList buildConfigGroups;
570     buildConfigGroups << "PROJECT" << "TARGET";
571 
572     t << "// !$*UTF8*$!\n"
573       << "{\n"
574       << "\t" << writeSettings("archiveVersion", "1", SettingsNoQuote) << ";\n"
575       << "\tclasses = {\n\t};\n"
576       << "\t" << writeSettings("objectVersion", QString::number(pbVersion), SettingsNoQuote) << ";\n"
577       << "\tobjects = {\n";
578 
579     //MAKE QMAKE equivelant
580     if (!project->isActiveConfig("no_autoqmake")) {
581         QString mkfile = pbx_dir + Option::dir_sep + "qt_makeqmake.mak";
582         QFile mkf(mkfile);
583         if(mkf.open(QIODevice::WriteOnly | QIODevice::Text)) {
584             writingUnixMakefileGenerator = true;
585             debug_msg(1, "pbuilder: Creating file: %s", mkfile.toLatin1().constData());
586             QTextStream mkt(&mkf);
587             writeHeader(mkt);
588             mkt << "QMAKE    = " << var("QMAKE_QMAKE") << Qt::endl;
589             project->values("QMAKE_MAKE_QMAKE_EXTRA_COMMANDS")
590                 << "@echo 'warning: Xcode project has been regenerated, custom settings have been lost. " \
591                    "Use CONFIG+=no_autoqmake to prevent this behavior in the future, " \
592                    "at the cost of requiring manual project change tracking.'";
593             writeMakeQmake(mkt);
594             mkt.flush();
595             mkf.close();
596             writingUnixMakefileGenerator = false;
597         }
598         QString phase_key = keyFor("QMAKE_PBX_MAKEQMAKE_BUILDPHASE");
599         mkfile = fileFixify(mkfile);
600         project->values("QMAKE_PBX_PRESCRIPT_BUILDPHASES").append(phase_key);
601         t << "\t\t" << phase_key << " = {\n"
602           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
603           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
604           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
605           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
606           << "\t\t\t" << writeSettings("name", "Qt Qmake") << ";\n"
607           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << ";\n"
608           << "\t\t\t" << writeSettings("shellScript", "make -C " + IoUtils::shellQuoteUnix(Option::output_dir)
609                                                       + " -f " + IoUtils::shellQuoteUnix(mkfile)) << ";\n"
610           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
611           << "\t\t};\n";
612     }
613 
614     // FIXME: Move all file resolving logic out of ProjectBuilderSources::files(), as it
615     // doesn't have access to any of the information it needs to resolve relative paths.
616     project->values("QMAKE_INTERNAL_INCLUDED_FILES").prepend(project->projectFile());
617 
618     // Since we can't fileFixify inside ProjectBuilderSources::files(), we resolve the absolute paths here
619     project->values("QMAKE_INTERNAL_INCLUDED_FILES") = ProStringList(
620                 fileFixify(project->values("QMAKE_INTERNAL_INCLUDED_FILES").toQStringList(),
621                            FileFixifyFromOutdir | FileFixifyAbsolute));
622 
623     //DUMP SOURCES
624     QSet<QString> processedSources;
625     QMap<QString, ProStringList> groups;
626     QList<ProjectBuilderSources> sources;
627     sources.append(ProjectBuilderSources("SOURCES", true));
628     sources.append(ProjectBuilderSources("GENERATED_SOURCES", true));
629     sources.append(ProjectBuilderSources("GENERATED_FILES"));
630     sources.append(ProjectBuilderSources("HEADERS"));
631     if(!project->isEmpty("QMAKE_EXTRA_COMPILERS")) {
632         const ProStringList &quc = project->values("QMAKE_EXTRA_COMPILERS");
633         for (ProStringList::ConstIterator it = quc.begin(); it != quc.end(); ++it) {
634             if (project->isEmpty(ProKey(*it + ".output")))
635                 continue;
636             ProStringList &inputs = project->values(ProKey(*it + ".input"));
637             int input = 0;
638             while (input < inputs.size()) {
639                 if (project->isEmpty(inputs.at(input).toKey())) {
640                     ++input;
641                     continue;
642                 }
643                 bool duplicate = false;
644                 bool isObj = project->values(ProKey(*it + ".CONFIG")).indexOf("no_link") == -1;
645                 if (!isObj) {
646                     for (int i = 0; i < sources.size(); ++i) {
647                         if (sources.at(i).keyName() == inputs.at(input)) {
648                             duplicate = true;
649                             break;
650                         }
651                     }
652                 }
653                 if (!duplicate) {
654                     const ProStringList &outputs = project->values(ProKey(*it + ".variable_out"));
655                     for(int output = 0; output < outputs.size(); ++output) {
656                         if(outputs.at(output) != "OBJECT") {
657                             isObj = false;
658                             break;
659                         }
660                     }
661                     sources.append(ProjectBuilderSources(inputs.at(input).toQString(), true,
662                                                          (*it).toQString(), isObj));
663 
664                     if (isObj) {
665                         inputs.removeAt(input);
666                         continue;
667                     }
668                 }
669 
670                 ++input;
671             }
672         }
673     }
674     sources.append(ProjectBuilderSources("QMAKE_INTERNAL_INCLUDED_FILES"));
675     for(int source = 0; source < sources.size(); ++source) {
676         ProStringList &src_list = project->values(ProKey("QMAKE_PBX_" + sources.at(source).keyName()));
677         ProStringList &root_group_list = project->values("QMAKE_PBX_GROUPS");
678 
679         const QStringList &files = fileFixify(sources.at(source).files(project),
680                                               FileFixifyFromOutdir | FileFixifyAbsolute);
681         for(int f = 0; f < files.count(); ++f) {
682             QString file = files[f];
683             if(!sources.at(source).compilerName().isNull() &&
684                !verifyExtraCompiler(sources.at(source).compilerName(), file))
685                 continue;
686             if(file.endsWith(Option::prl_ext))
687                 continue;
688             if (processedSources.contains(file))
689                 continue;
690             processedSources.insert(file);
691 
692             bool in_root = true;
693             QString src_key = keyFor(file);
694 
695             QString name = file.split(Option::dir_sep).back();
696 
697             if (!project->isActiveConfig("flat")) {
698                 // Build group hierarchy for file references that match the source our build dir
699                 QString relativePath = fileFixify(file, FileFixifyToIndir | FileFixifyRelative);
700                 if (QDir::isRelativePath(relativePath) && relativePath.startsWith(QLatin1String("../")))
701                     relativePath = fileFixify(file, FileFixifyRelative); // Try build dir
702 
703                 if (QDir::isRelativePath(relativePath)
704                         && !relativePath.startsWith(QLatin1String("../"))
705                         && relativePath.indexOf(Option::dir_sep) != -1) {
706                     QString last_grp("QMAKE_PBX_" + sources.at(source).groupName() + "_HEIR_GROUP");
707                     QStringList dirs = relativePath.split(Option::dir_sep);
708                     name = dirs.back();
709                     dirs.pop_back(); //remove the file portion as it will be added via src_key
710                     for(QStringList::Iterator dir_it = dirs.begin(); dir_it != dirs.end(); ++dir_it) {
711                         QString new_grp(last_grp + Option::dir_sep + (*dir_it)), new_grp_key(keyFor(new_grp));
712                         if(dir_it == dirs.begin()) {
713                             if(!src_list.contains(new_grp_key))
714                                 src_list.append(new_grp_key);
715                         } else {
716                             if(!groups[last_grp].contains(new_grp_key))
717                                 groups[last_grp] += new_grp_key;
718                         }
719                         last_grp = new_grp;
720                     }
721                     if (groups[last_grp].contains(src_key))
722                         continue;
723                     groups[last_grp] += src_key;
724                     in_root = false;
725                 }
726             }
727             if (in_root) {
728                 if (src_list.contains(src_key))
729                     continue;
730                 src_list.append(src_key);
731             }
732             //source reference
733             t << "\t\t" << src_key << " = {\n"
734               << "\t\t\t" << writeSettings("isa", "PBXFileReference", SettingsNoQuote) << ";\n"
735               << "\t\t\t" << writeSettings("path", file) << ";\n";
736             if (name != file)
737                 t << "\t\t\t" << writeSettings("name", name) << ";\n";
738             t << "\t\t\t" << writeSettings("sourceTree", "<absolute>") << ";\n";
739             QString filetype = xcodeFiletypeForFilename(file);
740             if (!filetype.isNull())
741                 t << "\t\t\t" << writeSettings("lastKnownFileType", filetype) << ";\n";
742             t << "\t\t};\n";
743             if (sources.at(source).isBuildable()) { //build reference
744                 QString build_key = keyFor(file + ".BUILDABLE");
745                 t << "\t\t" << build_key << " = {\n"
746                   << "\t\t\t" << writeSettings("fileRef", src_key) << ";\n"
747                   << "\t\t\t" << writeSettings("isa", "PBXBuildFile", SettingsNoQuote) << ";\n"
748                   << "\t\t\tsettings = {\n"
749                   << "\t\t\t\t" << writeSettings("ATTRIBUTES", ProStringList(), SettingsAsList, 5) << ";\n"
750                   << "\t\t\t};\n"
751                   << "\t\t};\n";
752                 if (sources.at(source).isObjectOutput(file))
753                     project->values("QMAKE_PBX_OBJ").append(build_key);
754             }
755         }
756         if(!src_list.isEmpty()) {
757             QString group_key = keyFor(sources.at(source).groupName());
758             if(root_group_list.indexOf(group_key) == -1)
759                 root_group_list += group_key;
760 
761             ProStringList &group = groups[sources.at(source).groupName()];
762             for(int src = 0; src < src_list.size(); ++src) {
763                 if(group.indexOf(src_list.at(src)) == -1)
764                     group += src_list.at(src);
765             }
766         }
767     }
768     for (QMap<QString, ProStringList>::Iterator grp_it = groups.begin(); grp_it != groups.end(); ++grp_it) {
769         t << "\t\t" << keyFor(grp_it.key()) << " = {\n"
770           << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
771           << "\t\t\t" << writeSettings("children", grp_it.value(), SettingsAsList, 4) << ";\n"
772           << "\t\t\t" << writeSettings("name", grp_it.key().section(Option::dir_sep, -1)) << ";\n"
773           << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
774           << "\t\t};\n";
775     }
776 
777     //PREPROCESS BUILDPHASE (just a makefile)
778     {
779         QString mkfile = pbx_dir + Option::dir_sep + "qt_preprocess.mak";
780         QFile mkf(mkfile);
781         ProStringList outputPaths;
782         ProStringList inputPaths;
783         if(mkf.open(QIODevice::WriteOnly | QIODevice::Text)) {
784             writingUnixMakefileGenerator = true;
785             debug_msg(1, "pbuilder: Creating file: %s", mkfile.toLatin1().constData());
786             QTextStream mkt(&mkf);
787             writeHeader(mkt);
788             mkt << "MOC       = " << var("QMAKE_MOC") << Qt::endl;
789             mkt << "UIC       = " << var("QMAKE_UIC") << Qt::endl;
790             mkt << "LEX       = " << var("QMAKE_LEX") << Qt::endl;
791             mkt << "LEXFLAGS  = " << var("QMAKE_LEXFLAGS") << Qt::endl;
792             mkt << "YACC      = " << var("QMAKE_YACC") << Qt::endl;
793             mkt << "YACCFLAGS = " << var("QMAKE_YACCFLAGS") << Qt::endl;
794             mkt << "DEFINES       = "
795                 << varGlue("PRL_EXPORT_DEFINES","-D"," -D"," ")
796                 << varGlue("DEFINES","-D"," -D","") << Qt::endl;
797             mkt << "INCPATH       =";
798             {
799                 const ProStringList &incs = project->values("INCLUDEPATH");
800                 for (ProStringList::ConstIterator incit = incs.begin(); incit != incs.end(); ++incit)
801                     mkt << " -I" << escapeFilePath((*incit));
802             }
803             if(!project->isEmpty("QMAKE_FRAMEWORKPATH_FLAGS"))
804                mkt << " " << var("QMAKE_FRAMEWORKPATH_FLAGS");
805             mkt << Qt::endl;
806             mkt << "DEL_FILE  = " << var("QMAKE_DEL_FILE") << Qt::endl;
807             mkt << "MOVE      = " << var("QMAKE_MOVE") << Qt::endl << Qt::endl;
808             mkt << "preprocess: compilers\n";
809             mkt << "clean preprocess_clean: compiler_clean\n\n";
810             writeExtraTargets(mkt);
811             if(!project->isEmpty("QMAKE_EXTRA_COMPILERS")) {
812                 mkt << "compilers:";
813                 const ProStringList &compilers = project->values("QMAKE_EXTRA_COMPILERS");
814                 for(int compiler = 0; compiler < compilers.size(); ++compiler) {
815                     const ProStringList &tmp_out = project->values(ProKey(compilers.at(compiler) + ".output"));
816                     if (tmp_out.isEmpty())
817                         continue;
818                     const ProStringList &inputs = project->values(ProKey(compilers.at(compiler) + ".input"));
819                     for(int input = 0; input < inputs.size(); ++input) {
820                         const ProStringList &files = project->values(inputs.at(input).toKey());
821                         if (files.isEmpty())
822                             continue;
823                         for(int file = 0, added = 0; file < files.size(); ++file) {
824                             QString fn = files.at(file).toQString();
825                             if (!verifyExtraCompiler(compilers.at(compiler), fn))
826                                 continue;
827                             if(added && !(added % 3))
828                                 mkt << "\\\n\t";
829                             ++added;
830                             const QString file_name = fileFixify(fn, FileFixifyFromOutdir);
831                             const QString tmpOut = fileFixify(tmp_out.first().toQString(), FileFixifyFromOutdir);
832                             QString path = escapeDependencyPath(Option::fixPathToTargetOS(
833                                     replaceExtraCompilerVariables(tmpOut, file_name, QString(), NoShell)));
834                             mkt << ' ' << path;
835                             inputPaths << fn;
836                             outputPaths << path;
837                         }
838                     }
839                 }
840                 mkt << Qt::endl;
841                 writeExtraCompilerTargets(mkt);
842                 writingUnixMakefileGenerator = false;
843             }
844             mkt.flush();
845             mkf.close();
846         }
847         // Remove duplicates from build steps with "combine"
848         outputPaths.removeDuplicates();
849 
850         // Don't create cycles. We only have one qt_preprocess.mak which runs different compilers
851         // whose inputs may depend on the output of another. The "compilers" step will run all
852         // compilers anyway
853         inputPaths.removeEach(outputPaths);
854 
855         mkfile = fileFixify(mkfile);
856         QString phase_key = keyFor("QMAKE_PBX_PREPROCESS_TARGET");
857 //        project->values("QMAKE_PBX_BUILDPHASES").append(phase_key);
858         project->values("QMAKE_PBX_PRESCRIPT_BUILDPHASES").append(phase_key);
859         t << "\t\t" << phase_key << " = {\n"
860           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
861           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
862           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
863           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
864           << "\t\t\t" << writeSettings("name", "Qt Preprocessors") << ";\n"
865           << "\t\t\t" << writeSettings("inputPaths", inputPaths, SettingsAsList, 4) << ";\n"
866           << "\t\t\t" << writeSettings("outputPaths", outputPaths, SettingsAsList, 4) << ";\n"
867           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << ";\n"
868           << "\t\t\t" << writeSettings("shellScript", "make -C " + IoUtils::shellQuoteUnix(Option::output_dir)
869                                                       + " -f " + IoUtils::shellQuoteUnix(mkfile)) << ";\n"
870           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
871           << "\t\t};\n";
872    }
873 
874     //SOURCE BUILDPHASE
875     if(!project->isEmpty("QMAKE_PBX_OBJ")) {
876         QString grp = "Compile Sources", key = keyFor(grp);
877         project->values("QMAKE_PBX_BUILDPHASES").append(key);
878         t << "\t\t" << key << " = {\n"
879           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
880           << "\t\t\t" << writeSettings("files", fixListForOutput("QMAKE_PBX_OBJ"), SettingsAsList, 4) << ";\n"
881           << "\t\t\t" << writeSettings("isa", "PBXSourcesBuildPhase", SettingsNoQuote) << ";\n"
882           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
883           << "\t\t\t" << writeSettings("name", grp) << ";\n"
884           << "\t\t};\n";
885     }
886 
887     if(!project->isActiveConfig("staticlib")) { //DUMP LIBRARIES
888         const ProStringList defaultLibDirs = project->values("QMAKE_DEFAULT_LIBDIRS");
889         ProStringList &libdirs = project->values("QMAKE_PBX_LIBPATHS"),
890               &frameworkdirs = project->values("QMAKE_FRAMEWORKPATH");
891         static const char * const libs[] = { "LIBS", "LIBS_PRIVATE",
892                                              "QMAKE_LIBS", "QMAKE_LIBS_PRIVATE", nullptr };
893         for (int i = 0; libs[i]; i++) {
894             tmp = project->values(libs[i]);
895             for(int x = 0; x < tmp.count();) {
896                 bool libSuffixReplaced = false;
897                 bool remove = false;
898                 QString library, name;
899                 ProString opt = tmp[x];
900                 if(opt.startsWith("-L")) {
901                     QString r = opt.mid(2).toQString();
902                     fixForOutput(r);
903                     libdirs.append(r);
904                 } else if(opt.startsWith("-l")) {
905                     name = opt.mid(2).toQString();
906                     QString lib("lib" + name);
907                     for (ProStringList::Iterator lit = libdirs.begin(); lit != libdirs.end(); ++lit) {
908                         if(project->isActiveConfig("link_prl")) {
909                             const QString prlFilePath = QMakeMetaInfo::checkLib(
910                                 Option::normalizePath((*lit) + Option::dir_sep + lib
911                                                       + Option::prl_ext));
912                             if (replaceLibrarySuffix(prlFilePath, opt, name, library))
913                                 remove = true;
914                             libSuffixReplaced = true;
915                         }
916                         if(!remove) {
917                             QString extns[] = { ".dylib", ".so", ".a", QString() };
918                             for(int n = 0; !remove && !extns[n].isNull(); n++) {
919                                 QString tmp =  (*lit) + Option::dir_sep + lib + extns[n];
920                                 if(exists(tmp)) {
921                                     library = tmp;
922                                     debug_msg(1, "pbuilder: Found library (%s) via %s",
923                                               opt.toLatin1().constData(), library.toLatin1().constData());
924                                     remove = true;
925                                 }
926                             }
927                         }
928                     }
929                 } else if(opt.startsWith("-F")) {
930                     QString r;
931                     if(opt.size() > 2) {
932                         r = opt.mid(2).toQString();
933                     } else {
934                         if(x == tmp.count()-1)
935                             break;
936                         r = tmp[++x].toQString();
937                     }
938                     if(!r.isEmpty()) {
939                         fixForOutput(r);
940                         frameworkdirs.append(r);
941                     }
942                 } else if(opt == "-framework") {
943                     if(x == tmp.count()-1)
944                         break;
945                     const ProString &framework = tmp[x+1];
946                     ProStringList fdirs = frameworkdirs;
947                     fdirs << "/System/Library/Frameworks/" << "/Library/Frameworks/";
948                     for(int fdir = 0; fdir < fdirs.count(); fdir++) {
949                         if(exists(fdirs[fdir] + QDir::separator() + framework + ".framework")) {
950                             remove = true;
951                             library = fdirs[fdir] + Option::dir_sep + framework + ".framework";
952                             tmp.removeAt(x);
953                             break;
954                         }
955                     }
956                 } else if (!opt.startsWith('-')) {
957                     QString fn = opt.toQString();
958                     if (exists(fn)) {
959                         remove = true;
960                         library = fn;
961                     }
962                 }
963                 if(!library.isEmpty()) {
964                     if (!libSuffixReplaced) {
965                         const QFileInfo fi = fileInfo(library);
966                         const QString prlFilePath = QMakeMetaInfo::checkLib(
967                             Option::normalizePath(fi.absolutePath() + '/' + fi.completeBaseName()
968                                                   + Option::prl_ext));
969                         if (!prlFilePath.isEmpty()) {
970                             name = fi.completeBaseName().mid(3);
971                             replaceLibrarySuffix(prlFilePath, opt, name, library);
972                         }
973                     }
974                     const int slsh = library.lastIndexOf(Option::dir_sep);
975                     if(name.isEmpty()) {
976                         if(slsh != -1)
977                             name = library.right(library.length() - slsh - 1);
978                     }
979                     if(slsh != -1) {
980                         const QString path = QFileInfo(library.left(slsh)).absoluteFilePath();
981                         if (!path.isEmpty() && !libdirs.contains(path)
982                             && !defaultLibDirs.contains(path)) {
983                             libdirs += path;
984                         }
985                     }
986                     library = fileFixify(library, FileFixifyFromOutdir | FileFixifyAbsolute);
987                     QString key = keyFor(library);
988                     if (!project->values("QMAKE_PBX_LIBRARIES").contains(key)) {
989                         bool is_frmwrk = (library.endsWith(".framework"));
990                         t << "\t\t" << key << " = {\n"
991                           << "\t\t\t" << writeSettings("isa", "PBXFileReference", SettingsNoQuote) << ";\n"
992                           << "\t\t\t" << writeSettings("name", name) << ";\n"
993                           << "\t\t\t" << writeSettings("path", library) << ";\n"
994                           << "\t\t\t" << writeSettings("refType", QString::number(reftypeForFile(library)), SettingsNoQuote) << ";\n"
995                           << "\t\t\t" << writeSettings("sourceTree", "<absolute>") << ";\n";
996                         if (is_frmwrk)
997                             t << "\t\t\t" << writeSettings("lastKnownFileType", "wrapper.framework") << ";\n";
998                         t << "\t\t};\n";
999                         project->values("QMAKE_PBX_LIBRARIES").append(key);
1000                         QString build_key = keyFor(library + ".BUILDABLE");
1001                         t << "\t\t" << build_key << " = {\n"
1002                           << "\t\t\t" << writeSettings("fileRef", key) << ";\n"
1003                           << "\t\t\t" << writeSettings("isa", "PBXBuildFile", SettingsNoQuote) << ";\n"
1004                           << "\t\t\tsettings = {\n"
1005                           << "\t\t\t};\n"
1006                           << "\t\t};\n";
1007                         project->values("QMAKE_PBX_BUILD_LIBRARIES").append(build_key);
1008                     }
1009                 }
1010                 if(remove)
1011                     tmp.removeAt(x);
1012                 else
1013                     x++;
1014             }
1015             project->values(libs[i]) = tmp;
1016         }
1017     }
1018     //SUBLIBS BUILDPHASE (just another makefile)
1019     if(!project->isEmpty("SUBLIBS")) {
1020         QString mkfile = pbx_dir + Option::dir_sep + "qt_sublibs.mak";
1021         QFile mkf(mkfile);
1022         if(mkf.open(QIODevice::WriteOnly | QIODevice::Text)) {
1023             writingUnixMakefileGenerator = true;
1024             debug_msg(1, "pbuilder: Creating file: %s", mkfile.toLatin1().constData());
1025             QTextStream mkt(&mkf);
1026             writeHeader(mkt);
1027             mkt << "SUBLIBS= ";
1028             // ### This is missing the parametrization found in unixmake2.cpp
1029             tmp = project->values("SUBLIBS");
1030             for(int i = 0; i < tmp.count(); i++)
1031                 t << escapeFilePath("tmp/lib" + tmp[i] + ".a") << ' ';
1032             t << Qt::endl << Qt::endl;
1033             mkt << "sublibs: $(SUBLIBS)\n\n";
1034             tmp = project->values("SUBLIBS");
1035             for(int i = 0; i < tmp.count(); i++)
1036                 t << escapeFilePath("tmp/lib" + tmp[i] + ".a") + ":\n\t"
1037                   << var(ProKey("MAKELIB" + tmp[i])) << Qt::endl << Qt::endl;
1038             mkt.flush();
1039             mkf.close();
1040             writingUnixMakefileGenerator = false;
1041         }
1042         QString phase_key = keyFor("QMAKE_PBX_SUBLIBS_BUILDPHASE");
1043         mkfile = fileFixify(mkfile);
1044         project->values("QMAKE_PBX_PRESCRIPT_BUILDPHASES").append(phase_key);
1045         t << "\t\t" << phase_key << " = {\n"
1046           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1047           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
1048           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
1049           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1050           << "\t\t\t" << writeSettings("name", "Qt Sublibs") << ";\n"
1051           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << "\n"
1052           << "\t\t\t" << writeSettings("shellScript", "make -C " + IoUtils::shellQuoteUnix(Option::output_dir)
1053                                                       + " -f " + IoUtils::shellQuoteUnix(mkfile)) << ";\n"
1054           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
1055           << "\t\t};\n";
1056     }
1057 
1058     if (!project->isEmpty("QMAKE_PRE_LINK")) {
1059         QString phase_key = keyFor("QMAKE_PBX_PRELINK_BUILDPHASE");
1060         project->values("QMAKE_PBX_BUILDPHASES").append(phase_key);
1061 
1062         ProStringList inputPaths;
1063         ProStringList outputPaths;
1064         const ProStringList &archs = project->values("QMAKE_XCODE_ARCHS");
1065         if (!archs.isEmpty()) {
1066             const int size = archs.size();
1067             inputPaths.reserve(size);
1068             outputPaths.reserve(size);
1069             for (int i = 0; i < size; ++i) {
1070                 const ProString &arch = archs.at(i);
1071                 inputPaths << "$(OBJECT_FILE_DIR_$(CURRENT_VARIANT))/" + arch + "/";
1072                 outputPaths << "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_" + arch + ")";
1073             }
1074         } else {
1075             inputPaths << "$(OBJECT_FILE_DIR_$(CURRENT_VARIANT))/$(CURRENT_ARCH)/";
1076             outputPaths << "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(CURRENT_ARCH))";
1077         }
1078 
1079         t << "\t\t" << phase_key << " = {\n"
1080           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1081           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
1082           // The build phases are not executed in the order they are defined, but by their
1083           // resolved dependenices, so we have to ensure that this phase is run after the
1084           // compilation phase, and before the link phase. Making the phase depend on the
1085           // object file directory, and "write" to the list of files to link achieves that.
1086           << "\t\t\t" << writeSettings("inputPaths", inputPaths, SettingsAsList, 4) << ";\n"
1087           << "\t\t\t" << writeSettings("outputPaths", outputPaths, SettingsAsList, 4) << ";\n"
1088           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
1089           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1090           << "\t\t\t" << writeSettings("name", "Qt Prelink") << ";\n"
1091           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << ";\n"
1092           << "\t\t\t" << writeSettings("shellScript", project->values("QMAKE_PRE_LINK")) << ";\n"
1093           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
1094           << "\t\t};\n";
1095     }
1096 
1097     //LIBRARY BUILDPHASE
1098     if(!project->isEmpty("QMAKE_PBX_LIBRARIES")) {
1099         tmp = project->values("QMAKE_PBX_LIBRARIES");
1100         if(!tmp.isEmpty()) {
1101             QString grp("Frameworks"), key = keyFor(grp);
1102             project->values("QMAKE_PBX_GROUPS").append(key);
1103             t << "\t\t" << key << " = {\n"
1104               << "\t\t\t" << writeSettings("children", project->values("QMAKE_PBX_LIBRARIES"), SettingsAsList, 4) << ";\n"
1105               << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
1106               << "\t\t\t" << writeSettings("name", grp) << ";\n"
1107               << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
1108               << "\t\t};\n";
1109         }
1110     }
1111     {
1112         QString grp("Link Binary With Libraries"), key = keyFor(grp);
1113         project->values("QMAKE_PBX_BUILDPHASES").append(key);
1114         t << "\t\t" << key << " = {\n"
1115           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1116           << "\t\t\t" << writeSettings("files", project->values("QMAKE_PBX_BUILD_LIBRARIES"), SettingsAsList, 4) << ";\n"
1117           << "\t\t\t" << writeSettings("isa", "PBXFrameworksBuildPhase", SettingsNoQuote) << ";\n"
1118           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1119           << "\t\t\t" << writeSettings("name", grp) << ";\n"
1120           << "\t\t};\n";
1121     }
1122 
1123     if (!project->isEmpty("QMAKE_POST_LINK")) {
1124         QString phase_key = keyFor("QMAKE_PBX_POSTLINK_BUILDPHASE");
1125         project->values("QMAKE_PBX_BUILDPHASES").append(phase_key);
1126         t << "\t\t" << phase_key << " = {\n"
1127           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1128           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
1129           // The build phases are not executed in the order they are defined, but by their
1130           // resolved dependenices, so we have to ensure the phase is run after linking.
1131           << "\t\t\t" << writeSettings("inputPaths", ProStringList("$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)"), SettingsAsList, 4) << ";\n"
1132           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
1133           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1134           << "\t\t\t" << writeSettings("name", "Qt Postlink") << ";\n"
1135           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << ";\n"
1136           << "\t\t\t" << writeSettings("shellScript", project->values("QMAKE_POST_LINK")) << ";\n"
1137           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
1138           << "\t\t};\n";
1139     }
1140 
1141     if (!project->isEmpty("DESTDIR")) {
1142         QString phase_key = keyFor("QMAKE_PBX_TARGET_COPY_PHASE");
1143         QString destDir = fileFixify(project->first("DESTDIR").toQString(),
1144                                      FileFixifyFromOutdir | FileFixifyAbsolute);
1145         project->values("QMAKE_PBX_BUILDPHASES").append(phase_key);
1146         t << "\t\t" << phase_key << " = {\n"
1147           << "\t\t\t" << writeSettings("isa", "PBXShellScriptBuildPhase", SettingsNoQuote) << ";\n"
1148           << "\t\t\t" << writeSettings("name", "Project Copy") << ";\n"
1149           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1150           << "\t\t\t" << writeSettings("files", ProStringList(), SettingsAsList, 4) << ";\n"
1151           << "\t\t\t" << writeSettings("inputPaths", ProStringList(), SettingsAsList, 4) << ";\n"
1152           << "\t\t\t" << writeSettings("outputPaths", ProStringList(), SettingsAsList, 4) << ";\n"
1153           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1154           << "\t\t\t" << writeSettings("shellPath", "/bin/sh") << ";\n"
1155           << "\t\t\t" << writeSettings("shellScript", fixForOutput("cp -r $BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME " + IoUtils::shellQuoteUnix(destDir))) << ";\n"
1156           << "\t\t\t" << writeSettings("showEnvVarsInLog", "0") << ";\n"
1157           << "\t\t};\n";
1158     }
1159     bool copyBundleResources = project->isActiveConfig("app_bundle") && project->first("TEMPLATE") == "app";
1160     ProStringList bundle_resources_files;
1161     ProStringList embedded_frameworks;
1162     QMap<ProString, ProStringList> embedded_plugins;
1163     // Copy Bundle Data
1164     if (!project->isEmpty("QMAKE_BUNDLE_DATA")) {
1165         ProStringList bundle_file_refs;
1166         bool osx = project->isActiveConfig("osx");
1167 
1168         //all bundle data
1169         const ProStringList &bundle_data = project->values("QMAKE_BUNDLE_DATA");
1170         for(int i = 0; i < bundle_data.count(); i++) {
1171             ProStringList bundle_files;
1172             ProString path = project->first(ProKey(bundle_data[i] + ".path"));
1173             const bool isEmbeddedFramework = ((!osx && path == QLatin1String("Frameworks"))
1174                 || (osx && path == QLatin1String("Contents/Frameworks")));
1175             const ProString pluginsPrefix = ProString(osx ? QLatin1String("Contents/PlugIns") : QLatin1String("PlugIns"));
1176             const bool isEmbeddedPlugin = (path == pluginsPrefix) || path.startsWith(pluginsPrefix + "/");
1177 
1178             //all files
1179             const ProStringList &files = project->values(ProKey(bundle_data[i] + ".files"));
1180             for(int file = 0; file < files.count(); file++) {
1181                 QString fn = fileFixify(files[file].toQString(), FileFixifyAbsolute);
1182                 QString name = fn.split(Option::dir_sep).back();
1183                 QString file_ref_key = keyFor("QMAKE_PBX_BUNDLE_DATA_FILE_REF." + bundle_data[i] + "-" + fn);
1184                 bundle_file_refs += file_ref_key;
1185                 t << "\t\t" << file_ref_key << " = {\n"
1186                   << "\t\t\t" << writeSettings("isa", "PBXFileReference", SettingsNoQuote) << ";\n"
1187                   << "\t\t\t" << writeSettings("path", fn) << ";\n"
1188                   << "\t\t\t" << writeSettings("name", name) << ";\n"
1189                   << "\t\t\t" << writeSettings("sourceTree", "<absolute>") << ";\n"
1190                   << "\t\t};\n";
1191                 QString file_key = keyFor("QMAKE_PBX_BUNDLE_DATA_FILE." + bundle_data[i] + "-" + fn);
1192                 bundle_files += file_key;
1193                 t << "\t\t" <<  file_key << " = {\n"
1194                   << "\t\t\t" << writeSettings("fileRef", file_ref_key) << ";\n"
1195                   << "\t\t\t" << writeSettings("isa", "PBXBuildFile", SettingsNoQuote) << ";\n";
1196                 if (isEmbeddedFramework || isEmbeddedPlugin || name.endsWith(".dylib") || name.endsWith(".framework"))
1197                     t << "\t\t\t" << writeSettings("settings", "{ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }", SettingsNoQuote) << ";\n";
1198                 t << "\t\t};\n";
1199             }
1200 
1201             if (copyBundleResources && ((!osx && path.isEmpty())
1202                                         || (osx && path == QLatin1String("Contents/Resources")))) {
1203                 for (const ProString &s : qAsConst(bundle_files))
1204                     bundle_resources_files << s;
1205             } else if (copyBundleResources && isEmbeddedFramework) {
1206                 for (const ProString &s : qAsConst(bundle_files))
1207                     embedded_frameworks << s;
1208             } else if (copyBundleResources && isEmbeddedPlugin) {
1209                 for (const ProString &s : qAsConst(bundle_files)) {
1210                     ProString subpath = (path == pluginsPrefix) ? ProString() : path.mid(pluginsPrefix.size() + 1);
1211                     embedded_plugins[subpath] << s;
1212                 }
1213             } else {
1214                 QString phase_key = keyFor("QMAKE_PBX_BUNDLE_COPY." + bundle_data[i]);
1215                 //if (!project->isActiveConfig("shallow_bundle")
1216                 //    && !project->isEmpty(ProKey(bundle_data[i] + ".version"))) {
1217                 //}
1218 
1219                 project->values("QMAKE_PBX_BUILDPHASES").append(phase_key);
1220                 t << "\t\t" << phase_key << " = {\n"
1221                   << "\t\t\t" << writeSettings("name", "Copy '" + bundle_data[i] + "' Files to Bundle") << ";\n"
1222                   << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1223                   << "\t\t\t" << writeSettings("dstPath", path) << ";\n"
1224                   << "\t\t\t" << writeSettings("dstSubfolderSpec", "1", SettingsNoQuote) << ";\n"
1225                   << "\t\t\t" << writeSettings("isa", "PBXCopyFilesBuildPhase", SettingsNoQuote) << ";\n"
1226                   << "\t\t\t" << writeSettings("files", bundle_files, SettingsAsList, 4) << ";\n"
1227                   << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1228                   << "\t\t};\n";
1229             }
1230         }
1231 
1232         QString bundle_data_key = keyFor("QMAKE_PBX_BUNDLE_DATA");
1233         project->values("QMAKE_PBX_GROUPS").append(bundle_data_key);
1234         t << "\t\t" << bundle_data_key << " = {\n"
1235           << "\t\t\t" << writeSettings("children", bundle_file_refs, SettingsAsList, 4) << ";\n"
1236           << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
1237           << "\t\t\t" << writeSettings("name", "Bundle Data") << ";\n"
1238           << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
1239           << "\t\t};\n";
1240     }
1241 
1242     // Copy bundle resources. Optimizes resources, and puts them into the Resources
1243     // subdirectory on OSX, but doesn't support paths.
1244     if (copyBundleResources) {
1245         if (!project->isEmpty("ICON")) {
1246             ProString icon = project->first("ICON");
1247             bundle_resources_files += keyFor(icon + ".BUILDABLE");
1248         }
1249 
1250         // Always add "Copy Bundle Resources" phase, even when we have no bundle
1251         // resources, since Xcode depends on it being there for e.g asset catalogs.
1252         QString grp("Copy Bundle Resources"), key = keyFor(grp);
1253         project->values("QMAKE_PBX_BUILDPHASES").append(key);
1254         t << "\t\t" << key << " = {\n"
1255           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1256           << "\t\t\t" << writeSettings("files", bundle_resources_files, SettingsAsList, 4) << ";\n"
1257           << "\t\t\t" << writeSettings("isa", "PBXResourcesBuildPhase", SettingsNoQuote) << ";\n"
1258           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1259           << "\t\t\t" << writeSettings("name", grp) << ";\n"
1260           << "\t\t};\n";
1261 
1262         QString grp2("Embed Frameworks"), key2 = keyFor(grp2);
1263         project->values("QMAKE_PBX_BUILDPHASES").append(key2);
1264         t << "\t\t" << key2 << " = {\n"
1265           << "\t\t\t" << writeSettings("isa", "PBXCopyFilesBuildPhase", SettingsNoQuote) << ";\n"
1266           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1267           << "\t\t\t" << writeSettings("dstPath", "") << ";\n"
1268           << "\t\t\t" << writeSettings("dstSubfolderSpec", "10", SettingsNoQuote) << ";\n"
1269           << "\t\t\t" << writeSettings("files", embedded_frameworks, SettingsAsList, 4) << ";\n"
1270           << "\t\t\t" << writeSettings("name", grp2) << ";\n"
1271           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1272           << "\t\t};\n";
1273 
1274         for (auto it = embedded_plugins.cbegin(), end = embedded_plugins.cend(); it != end; ++it) {
1275             QString suffix = !it.key().isEmpty() ? (" (" + it.key() + ")") : QString();
1276             QString grp3("Embed PlugIns" + suffix), key3 = keyFor(grp3);
1277             project->values("QMAKE_PBX_BUILDPHASES").append(key3);
1278             t << "\t\t" << key3 << " = {\n"
1279               << "\t\t\t" << writeSettings("isa", "PBXCopyFilesBuildPhase", SettingsNoQuote) << ";\n"
1280               << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1281               << "\t\t\t" << writeSettings("dstPath", it.key()) << ";\n"
1282               << "\t\t\t" << writeSettings("dstSubfolderSpec", "13", SettingsNoQuote) << ";\n"
1283               << "\t\t\t" << writeSettings("files", it.value(), SettingsAsList, 4) << ";\n"
1284               << "\t\t\t" << writeSettings("name", grp3) << ";\n"
1285               << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1286               << "\t\t};\n";
1287         }
1288     }
1289 
1290     //REFERENCE
1291     project->values("QMAKE_PBX_PRODUCTS").append(keyFor(pbx_dir + "QMAKE_PBX_REFERENCE"));
1292     t << "\t\t" << keyFor(pbx_dir + "QMAKE_PBX_REFERENCE") << " = {\n"
1293       << "\t\t\t" << writeSettings("isa",  "PBXFileReference", SettingsNoQuote) << ";\n"
1294       << "\t\t\t" << writeSettings("includeInIndex",  "0", SettingsNoQuote) << ";\n";
1295     if(project->first("TEMPLATE") == "app") {
1296         ProString targ = project->first("QMAKE_ORIG_TARGET");
1297         if(project->isActiveConfig("bundle") && !project->isEmpty("QMAKE_BUNDLE_EXTENSION")) {
1298             if(!project->isEmpty("QMAKE_BUNDLE_NAME"))
1299                 targ = project->first("QMAKE_BUNDLE_NAME");
1300             targ += project->first("QMAKE_BUNDLE_EXTENSION");
1301             if(!project->isEmpty("QMAKE_PBX_BUNDLE_TYPE"))
1302                 t << "\t\t\t" << writeSettings("explicitFileType", project->first("QMAKE_PBX_BUNDLE_TYPE")) + ";\n";
1303         } else if(project->isActiveConfig("app_bundle")) {
1304             if(!project->isEmpty("QMAKE_APPLICATION_BUNDLE_NAME"))
1305                 targ = project->first("QMAKE_APPLICATION_BUNDLE_NAME");
1306             targ += ".app";
1307             t << "\t\t\t" << writeSettings("explicitFileType", "wrapper.application") << ";\n";
1308         } else {
1309             t << "\t\t\t" << writeSettings("explicitFileType", "compiled.mach-o.executable") << ";\n";
1310         }
1311         t << "\t\t\t" << writeSettings("path", targ) << ";\n";
1312     } else {
1313         ProString lib = project->first("QMAKE_ORIG_TARGET");
1314         if(project->isActiveConfig("staticlib")) {
1315             lib = project->first("TARGET");
1316         } else if(!project->isActiveConfig("lib_bundle")) {
1317             if(project->isActiveConfig("plugin"))
1318                 lib = project->first("TARGET");
1319             else
1320                 lib = project->first("TARGET_");
1321         }
1322         int slsh = lib.lastIndexOf(Option::dir_sep);
1323         if(slsh != -1)
1324             lib = lib.right(lib.length() - slsh - 1);
1325         if(project->isActiveConfig("bundle") && !project->isEmpty("QMAKE_BUNDLE_EXTENSION")) {
1326             if(!project->isEmpty("QMAKE_BUNDLE_NAME"))
1327                 lib = project->first("QMAKE_BUNDLE_NAME");
1328             lib += project->first("QMAKE_BUNDLE_EXTENSION");
1329             if(!project->isEmpty("QMAKE_PBX_BUNDLE_TYPE"))
1330                 t << "\t\t\t" << writeSettings("explicitFileType", project->first("QMAKE_PBX_BUNDLE_TYPE")) << ";\n";
1331         } else if(!project->isActiveConfig("staticlib") && project->isActiveConfig("lib_bundle")) {
1332             if(!project->isEmpty("QMAKE_FRAMEWORK_BUNDLE_NAME"))
1333                 lib = project->first("QMAKE_FRAMEWORK_BUNDLE_NAME");
1334             lib += ".framework";
1335             t << "\t\t\t" << writeSettings("explicitFileType", "wrapper.framework") << ";\n";
1336         } else {
1337             t << "\t\t\t" << writeSettings("explicitFileType", "compiled.mach-o.dylib") << ";\n";
1338         }
1339         t << "\t\t\t" << writeSettings("path", lib) << ";\n";
1340     }
1341     t  << "\t\t\t" << writeSettings("sourceTree", "BUILT_PRODUCTS_DIR", SettingsNoQuote) << ";\n"
1342       << "\t\t};\n";
1343     { //Products group
1344         QString grp("Products"), key = keyFor(grp);
1345         project->values("QMAKE_PBX_GROUPS").append(key);
1346         t << "\t\t" << key << " = {\n"
1347           << "\t\t\t" << writeSettings("children", project->values("QMAKE_PBX_PRODUCTS"), SettingsAsList, 4) << ";\n"
1348           << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
1349           << "\t\t\t" << writeSettings("name", "Products") << ";\n"
1350           << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
1351           << "\t\t};\n";
1352     }
1353 
1354     //DUMP EVERYTHING THAT TIES THE ABOVE TOGETHER
1355     //ROOT_GROUP
1356     t << "\t\t" << keyFor("QMAKE_PBX_ROOT_GROUP") << " = {\n"
1357       << "\t\t\t" << writeSettings("children", project->values("QMAKE_PBX_GROUPS"), SettingsAsList, 4) << ";\n"
1358       << "\t\t\t" << writeSettings("isa", "PBXGroup", SettingsNoQuote) << ";\n"
1359       << "\t\t\t" << writeSettings("name", project->first("QMAKE_ORIG_TARGET")) << ";\n"
1360       << "\t\t\t" << writeSettings("sourceTree", "<group>") << ";\n"
1361       << "\t\t};\n";
1362 
1363     {
1364         QString aggregate_target_key = keyFor(pbx_dir + "QMAKE_PBX_AGGREGATE_TARGET");
1365         project->values("QMAKE_PBX_TARGETS").append(aggregate_target_key);
1366         t << "\t\t" << aggregate_target_key << " = {\n"
1367           << "\t\t\t" << writeSettings("buildPhases", project->values("QMAKE_PBX_PRESCRIPT_BUILDPHASES"), SettingsAsList, 4) << ";\n"
1368           << "\t\t\t" << writeSettings("dependencies", ProStringList(), SettingsAsList, 4) << ";\n"
1369           << "\t\t\t" << writeSettings("buildConfigurationList", keyFor("QMAKE_PBX_BUILDCONFIG_LIST_TARGET"), SettingsNoQuote) << ";\n"
1370           << "\t\t\t" << writeSettings("isa", "PBXAggregateTarget", SettingsNoQuote) << ";\n"
1371           << "\t\t\t" << writeSettings("buildRules", ProStringList(), SettingsAsList) << ";\n"
1372           << "\t\t\t" << writeSettings("productName", "Qt Preprocess") << ";\n"
1373           << "\t\t\t" << writeSettings("name", "Qt Preprocess") << ";\n"
1374           << "\t\t};\n";
1375 
1376         QString aggregate_target_dep_key = keyFor(pbx_dir + "QMAKE_PBX_AGGREGATE_TARGET_DEP");
1377         t << "\t\t" << aggregate_target_dep_key  << " = {\n"
1378           << "\t\t\t" << writeSettings("isa", "PBXTargetDependency", SettingsNoQuote) << ";\n"
1379           << "\t\t\t" << writeSettings("target", aggregate_target_key) << ";\n"
1380           << "\t\t};\n";
1381 
1382         project->values("QMAKE_PBX_TARGET_DEPENDS").append(aggregate_target_dep_key);
1383     }
1384 
1385     //TARGET
1386     QString target_key = keyFor(pbx_dir + "QMAKE_PBX_TARGET");
1387     project->values("QMAKE_PBX_TARGETS").prepend(target_key);
1388     t << "\t\t" << target_key << " = {\n"
1389       << "\t\t\t" << writeSettings("buildPhases", project->values("QMAKE_PBX_BUILDPHASES"),
1390                                    SettingsAsList, 4) << ";\n";
1391     t << "\t\t\t" << writeSettings("dependencies", project->values("QMAKE_PBX_TARGET_DEPENDS"), SettingsAsList, 4) << ";\n"
1392       << "\t\t\t" << writeSettings("productReference", keyFor(pbx_dir + "QMAKE_PBX_REFERENCE")) << ";\n";
1393     t << "\t\t\t" << writeSettings("buildConfigurationList", keyFor("QMAKE_PBX_BUILDCONFIG_LIST_TARGET"), SettingsNoQuote) << ";\n";
1394     t << "\t\t\t" << writeSettings("isa", "PBXNativeTarget", SettingsNoQuote) << ";\n";
1395     t << "\t\t\t" << writeSettings("buildRules", ProStringList(), SettingsAsList) << ";\n";
1396     if(project->first("TEMPLATE") == "app") {
1397         if (!project->isEmpty("QMAKE_PBX_PRODUCT_TYPE")) {
1398             t << "\t\t\t" << writeSettings("productType", project->first("QMAKE_PBX_PRODUCT_TYPE")) << ";\n";
1399         } else {
1400             if (project->isActiveConfig("app_bundle"))
1401                 t << "\t\t\t" << writeSettings("productType",  "com.apple.product-type.application") << ";\n";
1402             else
1403                 t << "\t\t\t" << writeSettings("productType", "com.apple.product-type.tool") << ";\n";
1404         }
1405         t << "\t\t\t" << writeSettings("name", project->first("QMAKE_ORIG_TARGET")) << ";\n"
1406           << "\t\t\t" << writeSettings("productName", project->first("QMAKE_ORIG_TARGET")) << ";\n";
1407     } else {
1408         ProString lib = project->first("QMAKE_ORIG_TARGET");
1409         if(!project->isActiveConfig("lib_bundle") && !project->isActiveConfig("staticlib"))
1410            lib.prepend("lib");
1411         t << "\t\t\t" << writeSettings("name", lib) << ";\n"
1412           << "\t\t\t" << writeSettings("productName", lib) << ";\n";
1413         if (!project->isEmpty("QMAKE_PBX_PRODUCT_TYPE"))
1414             t << "\t\t\t" << writeSettings("productType", project->first("QMAKE_PBX_PRODUCT_TYPE")) << ";\n";
1415         else if (project->isActiveConfig("staticlib"))
1416             t << "\t\t\t" << writeSettings("productType", "com.apple.product-type.library.static") << ";\n";
1417         else if (project->isActiveConfig("lib_bundle"))
1418             t << "\t\t\t" << writeSettings("productType", "com.apple.product-type.framework") << ";\n";
1419         else
1420             t << "\t\t\t" << writeSettings("productType", "com.apple.product-type.library.dynamic") << ";\n";
1421     }
1422     if(!project->isEmpty("DESTDIR"))
1423         t << "\t\t\t" << writeSettings("productInstallPath", project->first("DESTDIR")) << ";\n";
1424     t << "\t\t};\n";
1425 
1426     // Test target for running Qt unit tests under XCTest
1427     if (project->isActiveConfig("testcase") && project->isActiveConfig("app_bundle")) {
1428         QString devNullFileReferenceKey = keyFor(pbx_dir + "QMAKE_PBX_DEV_NULL_FILE_REFERENCE");
1429         t << "\t\t" << devNullFileReferenceKey << " = {\n"
1430           << "\t\t\t" << writeSettings("isa", "PBXFileReference", SettingsNoQuote) << ";\n"
1431           << "\t\t\t" << writeSettings("name", "/dev/null") << ";\n"
1432           << "\t\t\t" << writeSettings("path", "/dev/null") << ";\n"
1433           << "\t\t\t" << writeSettings("lastKnownFileType", "sourcecode.c.c") << ";\n"
1434           << "\t\t\t" << writeSettings("sourceTree", "<absolute>") << ";\n"
1435           << "\t\t};\n";
1436 
1437         QString devNullBuildFileKey = keyFor(pbx_dir + "QMAKE_PBX_DEV_NULL_BUILD_FILE");
1438         t << "\t\t" << devNullBuildFileKey << " = {\n"
1439           << "\t\t\t" << writeSettings("fileRef", devNullFileReferenceKey) << ";\n"
1440           << "\t\t\t" << writeSettings("isa", "PBXBuildFile", SettingsNoQuote) << ";\n"
1441           << "\t\t};\n";
1442 
1443         QString dummySourceBuildPhaseKey = keyFor(pbx_dir + "QMAKE_PBX_DUMMY_SOURCE_BUILD_PHASE");
1444         t << "\t\t" << dummySourceBuildPhaseKey << " = {\n"
1445           << "\t\t\t" << writeSettings("buildActionMask", "2147483647", SettingsNoQuote) << ";\n"
1446           << "\t\t\t" << writeSettings("files", devNullBuildFileKey, SettingsAsList, 4) << ";\n"
1447           << "\t\t\t" << writeSettings("isa", "PBXSourcesBuildPhase", SettingsNoQuote) << ";\n"
1448           << "\t\t\t" << writeSettings("runOnlyForDeploymentPostprocessing", "0", SettingsNoQuote) << ";\n"
1449           << "\t\t};\n";
1450 
1451         ProStringList testBundleBuildConfigs;
1452 
1453         ProString targetName = project->first("QMAKE_ORIG_TARGET");
1454         ProString testHost = "$(BUILT_PRODUCTS_DIR)/" + targetName + ".app/";
1455         if (project->isActiveConfig("osx"))
1456             testHost.append("Contents/MacOS/");
1457         testHost.append(targetName);
1458 
1459         static const char * const configs[] = { "Debug", "Release", nullptr };
1460         for (int i = 0; configs[i]; i++) {
1461             QString testBundleBuildConfig = keyFor(pbx_dir + "QMAKE_PBX_TEST_BUNDLE_BUILDCONFIG_" + configs[i]);
1462             t << "\t\t" << testBundleBuildConfig << " = {\n"
1463               << "\t\t\t" << writeSettings("isa", "XCBuildConfiguration", SettingsNoQuote) << ";\n"
1464               << "\t\t\tbuildSettings = {\n"
1465               << "\t\t\t\t" << writeSettings("INFOPLIST_FILE", project->first("QMAKE_XCODE_SPECDIR") + "/QtTest.plist") << ";\n"
1466               << "\t\t\t\t" << writeSettings("OTHER_LDFLAGS", "") << ";\n"
1467               << "\t\t\t\t" << writeSettings("TEST_HOST", testHost) << ";\n"
1468               << "\t\t\t\t" << writeSettings("DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym") << ";\n"
1469               << "\t\t\t};\n"
1470               << "\t\t\t" << writeSettings("name", configs[i], SettingsNoQuote) << ";\n"
1471               << "\t\t};\n";
1472 
1473               testBundleBuildConfigs.append(testBundleBuildConfig);
1474         }
1475 
1476         QString testBundleBuildConfigurationListKey = keyFor(pbx_dir + "QMAKE_PBX_TEST_BUNDLE_BUILDCONFIG_LIST");
1477         t << "\t\t" << testBundleBuildConfigurationListKey << " = {\n"
1478           << "\t\t\t" << writeSettings("isa", "XCConfigurationList", SettingsNoQuote) << ";\n"
1479           << "\t\t\t" << writeSettings("buildConfigurations", testBundleBuildConfigs, SettingsAsList, 4) << ";\n"
1480           << "\t\t\t" << writeSettings("defaultConfigurationIsVisible", "0", SettingsNoQuote) << ";\n"
1481           << "\t\t\t" << writeSettings("defaultConfigurationName", "Debug", SettingsNoQuote) << ";\n"
1482           << "\t\t};\n";
1483 
1484         QString primaryTargetDependencyKey = keyFor(pbx_dir + "QMAKE_PBX_PRIMARY_TARGET_DEP");
1485         t << "\t\t" << primaryTargetDependencyKey  << " = {\n"
1486           << "\t\t\t" << writeSettings("isa", "PBXTargetDependency", SettingsNoQuote) << ";\n"
1487           << "\t\t\t" << writeSettings("target", keyFor(pbx_dir + "QMAKE_PBX_TARGET")) << ";\n"
1488           << "\t\t};\n";
1489 
1490         QString testBundleReferenceKey = keyFor("QMAKE_TEST_BUNDLE_REFERENCE");
1491         t << "\t\t" << testBundleReferenceKey << " = {\n"
1492           << "\t\t\t" << writeSettings("isa",  "PBXFileReference", SettingsNoQuote) << ";\n"
1493           << "\t\t\t" << writeSettings("explicitFileType", "wrapper.cfbundle") << ";\n"
1494           << "\t\t\t" << writeSettings("includeInIndex",  "0", SettingsNoQuote) << ";\n"
1495           << "\t\t\t" << writeSettings("sourceTree", "BUILT_PRODUCTS_DIR", SettingsNoQuote) << ";\n"
1496           << "\t\t};\n";
1497 
1498         QString testTargetKey = keyFor(pbx_dir + "QMAKE_PBX_TEST_TARGET");
1499         project->values("QMAKE_PBX_TARGETS").append(testTargetKey);
1500         t << "\t\t" << testTargetKey << " = {\n"
1501           << "\t\t\t" << writeSettings("buildPhases", dummySourceBuildPhaseKey, SettingsAsList, 4) << ";\n"
1502           << "\t\t\t" << writeSettings("dependencies", primaryTargetDependencyKey, SettingsAsList, 4) << ";\n"
1503           << "\t\t\t" << writeSettings("buildConfigurationList", testBundleBuildConfigurationListKey) << ";\n"
1504           << "\t\t\t" << writeSettings("productType", "com.apple.product-type.bundle.unit-test") << ";\n"
1505           << "\t\t\t" << writeSettings("isa", "PBXNativeTarget", SettingsNoQuote) << ";\n"
1506           << "\t\t\t" << writeSettings("productReference", testBundleReferenceKey) << ";\n"
1507           << "\t\t\t" << writeSettings("name", "Qt Test") << ";\n"
1508           << "\t\t};\n";
1509 
1510         QLatin1String testTargetID("TestTargetID");
1511         project->values(ProKey("QMAKE_PBX_TARGET_ATTRIBUTES_" + testTargetKey + "_" + testTargetID)).append(keyFor(pbx_dir + "QMAKE_PBX_TARGET"));
1512         project->values(ProKey("QMAKE_PBX_TARGET_ATTRIBUTES_" + testTargetKey)).append(ProKey(testTargetID));
1513     }
1514 
1515     //DEBUG/RELEASE
1516     QString defaultConfig;
1517     for(int as_release = 0; as_release < 2; as_release++)
1518     {
1519         QString configName = (as_release ? "Release" : "Debug");
1520 
1521         QMap<QString, QString> settings;
1522         if (!project->isActiveConfig("no_xcode_development_team")) {
1523             QString teamId;
1524             if (!project->isEmpty("QMAKE_DEVELOPMENT_TEAM")) {
1525                 teamId = project->first("QMAKE_DEVELOPMENT_TEAM").toQString();
1526             } else {
1527                 const QList<QVariantMap> teams = provisioningTeams();
1528                 if (!teams.isEmpty()) // first suitable team we find is the one we'll use by default
1529                     teamId = teams.first().value(QLatin1String("teamID")).toString();
1530             }
1531             if (!teamId.isEmpty())
1532                 settings.insert("DEVELOPMENT_TEAM", teamId);
1533             if (!project->isEmpty("QMAKE_PROVISIONING_PROFILE"))
1534                 settings.insert("PROVISIONING_PROFILE_SPECIFIER", project->first("QMAKE_PROVISIONING_PROFILE").toQString());
1535         }
1536 
1537         settings.insert("APPLICATION_EXTENSION_API_ONLY", project->isActiveConfig("app_extension_api_only") ? "YES" : "NO");
1538         // required for tvOS (and watchos), optional on iOS (deployment target >= iOS 6.0)
1539         settings.insert("ENABLE_BITCODE", project->isActiveConfig("bitcode") ? "YES" : "NO");
1540         if(!as_release)
1541             settings.insert("GCC_OPTIMIZATION_LEVEL", "0");
1542         if(project->isActiveConfig("sdk") && !project->isEmpty("QMAKE_MAC_SDK"))
1543                 settings.insert("SDKROOT", project->first("QMAKE_MAC_SDK").toQString());
1544         {
1545             const ProStringList &l = project->values("QMAKE_MAC_XCODE_SETTINGS");
1546             for(int i = 0; i < l.size(); ++i) {
1547                 ProString name = l.at(i);
1548                 const ProKey buildKey(name + ".build");
1549                 if (!project->isEmpty(buildKey)) {
1550                     const QString build = project->first(buildKey).toQString();
1551                     if (build.toLower() != configName.toLower())
1552                         continue;
1553                 }
1554                 const QString value = project->values(ProKey(name + ".value")).join(QString(Option::field_sep));
1555                 const ProKey nkey(name + ".name");
1556                 if (!project->isEmpty(nkey))
1557                     name = project->first(nkey);
1558                 settings.insert(name.toQString(), value);
1559             }
1560         }
1561         if (project->first("TEMPLATE") == "app") {
1562             settings.insert("PRODUCT_NAME", fixForOutput(project->first("QMAKE_ORIG_TARGET").toQString()));
1563         } else {
1564             ProString lib = project->first("QMAKE_ORIG_TARGET");
1565             if (!project->isActiveConfig("lib_bundle") && !project->isActiveConfig("staticlib"))
1566                 lib.prepend("lib");
1567             settings.insert("PRODUCT_NAME", lib.toQString());
1568         }
1569 
1570         if (project->isActiveConfig("debug") != (bool)as_release)
1571             defaultConfig = configName;
1572         for (int i = 0; i < buildConfigGroups.size(); i++) {
1573             QString key = keyFor("QMAKE_PBX_BUILDCONFIG_" + configName + buildConfigGroups.at(i));
1574             project->values(ProKey("QMAKE_PBX_BUILDCONFIGS_" + buildConfigGroups.at(i))).append(key);
1575             t << "\t\t" << key << " = {\n"
1576               << "\t\t\t" << writeSettings("isa", "XCBuildConfiguration", SettingsNoQuote) << ";\n"
1577               << "\t\t\tbuildSettings = {\n";
1578             for (QMap<QString, QString>::Iterator set_it = settings.begin(); set_it != settings.end(); ++set_it)
1579                 t << "\t\t\t\t" << writeSettings(set_it.key(), set_it.value()) << ";\n";
1580             if (buildConfigGroups.at(i) == QLatin1String("PROJECT")) {
1581                 if (!project->isEmpty("QMAKE_XCODE_GCC_VERSION"))
1582                     t << "\t\t\t\t" << writeSettings("GCC_VERSION", project->first("QMAKE_XCODE_GCC_VERSION"), SettingsNoQuote) << ";\n";
1583                 ProString program = project->first("QMAKE_CC");
1584                 if (!program.isEmpty())
1585                     t << "\t\t\t\t" << writeSettings("CC", fixForOutput(findProgram(program))) << ";\n";
1586                 program = project->first("QMAKE_CXX");
1587                 // Xcode will automatically take care of using CC with the right -x option,
1588                 // and will actually break if we pass CPLUSPLUS, by adding an additional set of "++"
1589                 if (!program.isEmpty() && !program.contains("clang++"))
1590                     t << "\t\t\t\t" << writeSettings("CPLUSPLUS", fixForOutput(findProgram(program))) << ";\n";
1591                 program = project->first("QMAKE_LINK");
1592                 if (!program.isEmpty())
1593                     t << "\t\t\t\t" << writeSettings("LDPLUSPLUS", fixForOutput(findProgram(program))) << ";\n";
1594 
1595                 if ((project->first("TEMPLATE") == "app" && project->isActiveConfig("app_bundle")) ||
1596                    (project->first("TEMPLATE") == "lib" && !project->isActiveConfig("staticlib") &&
1597                     project->isActiveConfig("lib_bundle"))) {
1598                     QString plist = fileFixify(project->first("QMAKE_INFO_PLIST").toQString(), FileFixifyToIndir);
1599                     if (!plist.isEmpty()) {
1600                         if (exists(plist))
1601                             t << "\t\t\t\t" << writeSettings("INFOPLIST_FILE", fileFixify(plist)) << ";\n";
1602                         else
1603                             warn_msg(WarnLogic, "Could not resolve Info.plist: '%s'. Check if QMAKE_INFO_PLIST points to a valid file.", plist.toLatin1().constData());
1604                     } else {
1605                         plist = fileFixify(specdir() + QDir::separator() + "Info.plist." + project->first("TEMPLATE"), FileFixifyBackwards);
1606                         QFile plist_in_file(plist);
1607                         if (plist_in_file.open(QIODevice::ReadOnly)) {
1608                             QTextStream plist_in(&plist_in_file);
1609                             QString plist_in_text = plist_in.readAll();
1610                             plist_in_text.replace(QLatin1String("@ICON@"),
1611                               (project->isEmpty("ICON") ? QString("") : project->first("ICON").toQString().section(Option::dir_sep, -1)));
1612                             if (project->first("TEMPLATE") == "app") {
1613                                 ProString app_bundle_name = project->first("QMAKE_APPLICATION_BUNDLE_NAME");
1614                                 if (app_bundle_name.isEmpty())
1615                                     app_bundle_name = project->first("QMAKE_ORIG_TARGET");
1616                                 plist_in_text.replace(QLatin1String("@EXECUTABLE@"), app_bundle_name.toQString());
1617                             } else {
1618                                 ProString lib_bundle_name = project->first("QMAKE_FRAMEWORK_BUNDLE_NAME");
1619                                 if (lib_bundle_name.isEmpty())
1620                                     lib_bundle_name = project->first("QMAKE_ORIG_TARGET");
1621                                 plist_in_text.replace(QLatin1String("@LIBRARY@"), lib_bundle_name.toQString());
1622                             }
1623                             QString bundlePrefix = project->first("QMAKE_TARGET_BUNDLE_PREFIX").toQString();
1624                             if (bundlePrefix.isEmpty())
1625                                 bundlePrefix = "com.yourcompany";
1626                             plist_in_text.replace(QLatin1String("@BUNDLEIDENTIFIER@"), bundlePrefix + '.' + QLatin1String("${PRODUCT_NAME:rfc1034identifier}"));
1627                             if (!project->values("VERSION").isEmpty()) {
1628                                 plist_in_text.replace(QLatin1String("@SHORT_VERSION@"), project->first("VER_MAJ") + "." + project->first("VER_MIN"));
1629                             }
1630                             plist_in_text.replace(QLatin1String("@TYPEINFO@"),
1631                                 (project->isEmpty("QMAKE_PKGINFO_TYPEINFO")
1632                                     ? QString::fromLatin1("????") : project->first("QMAKE_PKGINFO_TYPEINFO").left(4).toQString()));
1633                             QFile plist_out_file(Option::output_dir + "/Info.plist");
1634                             if (plist_out_file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1635                                 QTextStream plist_out(&plist_out_file);
1636                                 plist_out << plist_in_text;
1637                                 t << "\t\t\t\t" << writeSettings("INFOPLIST_FILE", "Info.plist") << ";\n";
1638                             } else {
1639                                 warn_msg(WarnLogic, "Could not write Info.plist: '%s'.", plist_out_file.fileName().toLatin1().constData());
1640                             }
1641                         } else {
1642                             warn_msg(WarnLogic, "Could not open Info.plist: '%s'.", plist.toLatin1().constData());
1643                         }
1644                     }
1645                 }
1646 
1647                 if (Option::output_dir != qmake_getpwd()) {
1648                     // The SYMROOT is marked by Xcode as excluded from Time Machine
1649                     // backups, as it's a temporary build dir, but that's fine when
1650                     // we are shadow building.
1651                     t << "\t\t\t\t" << writeSettings("SYMROOT", "$(PROJECT_DIR)") << ";\n";
1652                 } else {
1653                     // For in-source builds we don't want to exclude the sources
1654                     // from being backed up, so we point SYMROOT to a temporary
1655                     // build directory.
1656                     t << "\t\t\t\t" << writeSettings("SYMROOT", ".xcode") << ";\n";
1657 
1658                     // Then we set the configuration build dir instead, so that the
1659                     // final build artifacts end up in the place Qt Creator expects.
1660                     // The disadvantage of using this over SYMROOT is that Xcode will
1661                     // fail to archive projects that override this variable.
1662                     t << "\t\t\t\t" << writeSettings("CONFIGURATION_BUILD_DIR",
1663                         "$(PROJECT_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)") << ";\n";
1664                 }
1665 
1666                 if (!project->isEmpty("DESTDIR")) {
1667                     ProString dir = project->first("DESTDIR");
1668                     if (QDir::isRelativePath(dir.toQString()))
1669                         dir.prepend(Option::output_dir + Option::dir_sep);
1670                     t << "\t\t\t\t" << writeSettings("INSTALL_DIR", dir) << ";\n";
1671                 }
1672 
1673                 if (project->first("TEMPLATE") == "lib")
1674                     t << "\t\t\t\t" << writeSettings("INSTALL_PATH", ProStringList()) << ";\n";
1675 
1676                 if (!project->isEmpty("VERSION") && project->first("VERSION") != "0.0.0") {
1677                     t << "\t\t\t\t" << writeSettings("DYLIB_CURRENT_VERSION",  project->first("VER_MAJ")+"."+project->first("VER_MIN")+"."+project->first("VER_PAT")) << ";\n";
1678                     if (project->isEmpty("COMPAT_VERSION"))
1679                         t << "\t\t\t\t" << writeSettings("DYLIB_COMPATIBILITY_VERSION", project->first("VER_MAJ")+"."+project->first("VER_MIN")) << ";\n";
1680                     if (project->first("TEMPLATE") == "lib" && !project->isActiveConfig("staticlib") &&
1681                        project->isActiveConfig("lib_bundle"))
1682                         t << "\t\t\t\t" << writeSettings("FRAMEWORK_VERSION", project->first("QMAKE_FRAMEWORK_VERSION")) << ";\n";
1683                 }
1684                 if (!project->isEmpty("COMPAT_VERSION"))
1685                     t << "\t\t\t\t" << writeSettings("DYLIB_COMPATIBILITY_VERSION", project->first("COMPAT_VERSION")) << ";\n";
1686 
1687                 if (!project->isEmpty("QMAKE_MACOSX_DEPLOYMENT_TARGET"))
1688                     t << "\t\t\t\t" << writeSettings("MACOSX_DEPLOYMENT_TARGET", project->first("QMAKE_MACOSX_DEPLOYMENT_TARGET")) << ";\n";
1689                 if (!project->isEmpty("QMAKE_IOS_DEPLOYMENT_TARGET"))
1690                     t << "\t\t\t\t" << writeSettings("IPHONEOS_DEPLOYMENT_TARGET", project->first("QMAKE_IOS_DEPLOYMENT_TARGET")) << ";\n";
1691                 if (!project->isEmpty("QMAKE_TVOS_DEPLOYMENT_TARGET"))
1692                     t << "\t\t\t\t" << writeSettings("APPLETVOS_DEPLOYMENT_TARGET", project->first("QMAKE_TVOS_DEPLOYMENT_TARGET")) << ";\n";
1693                 if (!project->isEmpty("QMAKE_WATCHOS_DEPLOYMENT_TARGET"))
1694                     t << "\t\t\t\t" << writeSettings("WATCHOS_DEPLOYMENT_TARGET", project->first("QMAKE_WATCHOS_DEPLOYMENT_TARGET")) << ";\n";
1695 
1696                 if (!project->isEmpty("QMAKE_XCODE_CODE_SIGN_IDENTITY"))
1697                     t << "\t\t\t\t" << writeSettings("CODE_SIGN_IDENTITY", project->first("QMAKE_XCODE_CODE_SIGN_IDENTITY")) << ";\n";
1698 
1699                 tmp = project->values("QMAKE_PBX_VARS");
1700                 for (int i = 0; i < tmp.count(); i++) {
1701                     QString var = tmp[i].toQString(), val = QString::fromLocal8Bit(qgetenv(var.toLatin1().constData()));
1702                     if (val.isEmpty() && var == "TB")
1703                         val = "/usr/bin/";
1704                     t << "\t\t\t\t" << writeSettings(var, val) << ";\n";
1705                 }
1706                 if (!project->isEmpty("PRECOMPILED_HEADER")) {
1707                     t << "\t\t\t\t" << writeSettings("GCC_PRECOMPILE_PREFIX_HEADER", "YES") << ";\n"
1708                     << "\t\t\t\t" << writeSettings("GCC_PREFIX_HEADER", project->first("PRECOMPILED_HEADER")) << ";\n";
1709                 }
1710                 t << "\t\t\t\t" << writeSettings("HEADER_SEARCH_PATHS", fixListForOutput("INCLUDEPATH"), SettingsAsList, 5) << ";\n"
1711                   << "\t\t\t\t" << writeSettings("LIBRARY_SEARCH_PATHS", fixListForOutput("QMAKE_PBX_LIBPATHS"), SettingsAsList, 5) << ";\n"
1712                   << "\t\t\t\t" << writeSettings("FRAMEWORK_SEARCH_PATHS", fixListForOutput("QMAKE_FRAMEWORKPATH"),
1713                         !project->values("QMAKE_FRAMEWORKPATH").isEmpty() ? SettingsAsList : 0, 5) << ";\n";
1714 
1715                 {
1716                     ProStringList cflags = project->values("QMAKE_CFLAGS");
1717                     const ProStringList &prl_defines = project->values("PRL_EXPORT_DEFINES");
1718                     for (int i = 0; i < prl_defines.size(); ++i)
1719                         cflags += "-D" + prl_defines.at(i);
1720                     const ProStringList &defines = project->values("DEFINES");
1721                     for (int i = 0; i < defines.size(); ++i)
1722                         cflags += "-D" + defines.at(i);
1723                     t << "\t\t\t\t" << writeSettings("OTHER_CFLAGS", fixListForOutput(cflags), SettingsAsList, 5) << ";\n";
1724                 }
1725                 {
1726                     ProStringList cxxflags = project->values("QMAKE_CXXFLAGS");
1727                     const ProStringList &prl_defines = project->values("PRL_EXPORT_DEFINES");
1728                     for (int i = 0; i < prl_defines.size(); ++i)
1729                         cxxflags += "-D" + prl_defines.at(i);
1730                     const ProStringList &defines = project->values("DEFINES");
1731                     for (int i = 0; i < defines.size(); ++i)
1732                         cxxflags += "-D" + defines.at(i);
1733                     t << "\t\t\t\t" << writeSettings("OTHER_CPLUSPLUSFLAGS", fixListForOutput(cxxflags), SettingsAsList, 5) << ";\n";
1734                 }
1735                 if (!project->isActiveConfig("staticlib")) {
1736                     t << "\t\t\t\t" << writeSettings("OTHER_LDFLAGS",
1737                                                      fixListForOutput("SUBLIBS")
1738                                                      + fixListForOutput("QMAKE_LFLAGS")
1739                                                      + fixListForOutput(fixLibFlags("LIBS"))
1740                                                      + fixListForOutput(fixLibFlags("LIBS_PRIVATE"))
1741                                                      + fixListForOutput(fixLibFlags("QMAKE_LIBS"))
1742                                                      + fixListForOutput(fixLibFlags("QMAKE_LIBS_PRIVATE")),
1743                                                      SettingsAsList, 6) << ";\n";
1744                 }
1745                 const ProStringList &archs = !project->values("QMAKE_XCODE_ARCHS").isEmpty() ?
1746                                                 project->values("QMAKE_XCODE_ARCHS") : project->values("QT_ARCH");
1747                 if (!archs.isEmpty())
1748                     t << "\t\t\t\t" << writeSettings("ARCHS", archs) << ";\n";
1749                 if (!project->isEmpty("OBJECTS_DIR"))
1750                     t << "\t\t\t\t" << writeSettings("OBJROOT", project->first("OBJECTS_DIR")) << ";\n";
1751             } else {
1752                 if (project->first("TEMPLATE") == "app") {
1753                     t << "\t\t\t\t" << writeSettings("PRODUCT_NAME", fixForOutput(project->first("QMAKE_ORIG_TARGET").toQString())) << ";\n";
1754                 } else {
1755                     if (!project->isActiveConfig("plugin") && project->isActiveConfig("staticlib"))
1756                         t << "\t\t\t\t" << writeSettings("LIBRARY_STYLE", "STATIC") << ";\n";
1757                     else
1758                         t << "\t\t\t\t" << writeSettings("LIBRARY_STYLE", "DYNAMIC") << ";\n";
1759                     ProString lib = project->first("QMAKE_ORIG_TARGET");
1760                     if (!project->isActiveConfig("lib_bundle") && !project->isActiveConfig("staticlib"))
1761                         lib.prepend("lib");
1762                     t << "\t\t\t\t" << writeSettings("PRODUCT_NAME", lib) << ";\n";
1763                 }
1764             }
1765             t << "\t\t\t};\n"
1766               << "\t\t\t" << writeSettings("name", configName) << ";\n"
1767               << "\t\t};\n";
1768         }
1769     }
1770     for (int i = 0; i < buildConfigGroups.size(); i++) {
1771         t << "\t\t" << keyFor("QMAKE_PBX_BUILDCONFIG_LIST_" + buildConfigGroups.at(i)) << " = {\n"
1772           << "\t\t\t" << writeSettings("isa", "XCConfigurationList", SettingsNoQuote) << ";\n"
1773           << "\t\t\t" << writeSettings("buildConfigurations", project->values(ProKey("QMAKE_PBX_BUILDCONFIGS_" + buildConfigGroups.at(i))), SettingsAsList, 4) << ";\n"
1774           << "\t\t\t" << writeSettings("defaultConfigurationIsVisible", "0", SettingsNoQuote) << ";\n"
1775           << "\t\t\t" << writeSettings("defaultConfigurationName", defaultConfig) << ";\n"
1776           << "\t\t};\n";
1777     }
1778     //ROOT
1779     t << "\t\t" << keyFor("QMAKE_PBX_ROOT") << " = {\n"
1780       << "\t\t\t" << writeSettings("hasScannedForEncodings", "1", SettingsNoQuote) << ";\n"
1781       << "\t\t\t" << writeSettings("compatibilityVersion", "Xcode 3.2") << ";\n"
1782       << "\t\t\t" << writeSettings("isa", "PBXProject", SettingsNoQuote) << ";\n"
1783       << "\t\t\t" << writeSettings("mainGroup", keyFor("QMAKE_PBX_ROOT_GROUP")) << ";\n"
1784       << "\t\t\t" << writeSettings("productRefGroup", keyFor("Products")) << ";\n";
1785     t << "\t\t\t" << writeSettings("buildConfigurationList", keyFor("QMAKE_PBX_BUILDCONFIG_LIST_PROJECT")) << ";\n";
1786     t << "\t\t\t" << writeSettings("projectDirPath", ProStringList()) << ";\n"
1787       << "\t\t\t" << writeSettings("projectRoot", "") << ";\n"
1788       << "\t\t\t" << writeSettings("targets", project->values("QMAKE_PBX_TARGETS"), SettingsAsList, 4) << ";\n"
1789       << "\t\t\t" << "attributes = {\n"
1790       << "\t\t\t\tTargetAttributes = {\n";
1791     for (const ProString &target : project->values("QMAKE_PBX_TARGETS")) {
1792         const ProStringList &attributes = project->values(ProKey("QMAKE_PBX_TARGET_ATTRIBUTES_" + target));
1793         if (attributes.isEmpty())
1794             continue;
1795         t << "\t\t\t\t\t" << target << " = {\n";
1796         for (const ProString &attribute : attributes)
1797             t << "\t\t\t\t\t\t" << writeSettings(attribute.toQString(), project->first(ProKey("QMAKE_PBX_TARGET_ATTRIBUTES_" + target + "_" + attribute))) << ";\n";
1798         t << "\t\t\t\t\t};\n";
1799     }
1800     t << "\t\t\t\t};\n"
1801       << "\t\t\t};\n"
1802       << "\t\t};\n";
1803 
1804     // FIXME: Deal with developmentRegion and knownRegions for QMAKE_PBX_ROOT
1805 
1806     //FOOTER
1807     t << "\t};\n"
1808       << "\t" << writeSettings("rootObject", keyFor("QMAKE_PBX_ROOT")) << ";\n"
1809       << "}\n";
1810 
1811     // Scheme
1812     {
1813         QString xcodeSpecDir = project->first("QMAKE_XCODE_SPECDIR").toQString();
1814 
1815         bool wroteCustomScheme = false;
1816 
1817         QString projectSharedSchemesPath = pbx_dir + "/xcshareddata/xcschemes";
1818         if (mkdir(projectSharedSchemesPath)) {
1819             QString target = project->first("QMAKE_ORIG_TARGET").toQString();
1820 
1821             QFile defaultSchemeFile(xcodeSpecDir + "/default.xcscheme");
1822             QFile outputSchemeFile(projectSharedSchemesPath + Option::dir_sep + target + ".xcscheme");
1823 
1824             if (defaultSchemeFile.open(QIODevice::ReadOnly)
1825                 && outputSchemeFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
1826 
1827                 QTextStream defaultSchemeStream(&defaultSchemeFile);
1828                 QString schemeData = defaultSchemeStream.readAll();
1829 
1830                 schemeData.replace(QLatin1String("@QMAKE_ORIG_TARGET@"), target);
1831                 schemeData.replace(QLatin1String("@TARGET_PBX_KEY@"), keyFor(pbx_dir + "QMAKE_PBX_TARGET"));
1832                 schemeData.replace(QLatin1String("@TEST_BUNDLE_PBX_KEY@"), keyFor("QMAKE_TEST_BUNDLE_REFERENCE"));
1833                 schemeData.replace(QLatin1String("@QMAKE_RELATIVE_PBX_DIR@"), fileFixify(pbx_dir));
1834 
1835                 QTextStream outputSchemeStream(&outputSchemeFile);
1836                 outputSchemeStream << schemeData;
1837 
1838                 wroteCustomScheme = true;
1839             }
1840         }
1841 
1842         if (wroteCustomScheme) {
1843              // Prevent Xcode from auto-generating schemes
1844             QString workspaceSettingsFilename("WorkspaceSettings.xcsettings");
1845             QString workspaceSharedDataPath = pbx_dir + "/project.xcworkspace/xcshareddata";
1846             if (mkdir(workspaceSharedDataPath)) {
1847                 QFile::copy(xcodeSpecDir + Option::dir_sep + workspaceSettingsFilename,
1848                             workspaceSharedDataPath + Option::dir_sep + workspaceSettingsFilename);
1849             } else {
1850                 wroteCustomScheme = false;
1851             }
1852         }
1853 
1854         if (!wroteCustomScheme)
1855             warn_msg(WarnLogic, "Failed to generate schemes in '%s', " \
1856                 "falling back to Xcode auto-generated schemes", qPrintable(projectSharedSchemesPath));
1857     }
1858 
1859     return true;
1860 }
1861 
1862 QString
findProgram(const ProString & prog)1863 ProjectBuilderMakefileGenerator::findProgram(const ProString &prog)
1864 {
1865     QString ret = prog.toQString();
1866     if(QDir::isRelativePath(ret)) {
1867         QStringList paths = QString(qgetenv("PATH")).split(':');
1868         for(int i = 0; i < paths.size(); ++i) {
1869             QString path = paths.at(i) + "/" + prog;
1870             if(exists(path)) {
1871                 ret = path;
1872                 break;
1873             }
1874         }
1875     }
1876     return ret;
1877 }
1878 
1879 QString
fixForOutput(const QString & values)1880 ProjectBuilderMakefileGenerator::fixForOutput(const QString &values)
1881 {
1882     //get the environment variables references
1883     QRegExp reg_var("\\$\\((.*)\\)");
1884     for(int rep = 0; (rep = reg_var.indexIn(values, rep)) != -1;) {
1885         if(project->values("QMAKE_PBX_VARS").indexOf(reg_var.cap(1)) == -1)
1886             project->values("QMAKE_PBX_VARS").append(reg_var.cap(1));
1887         rep += reg_var.matchedLength();
1888     }
1889 
1890     return values;
1891 }
1892 
1893 ProStringList
fixListForOutput(const char * where)1894 ProjectBuilderMakefileGenerator::fixListForOutput(const char *where)
1895 {
1896     return fixListForOutput(project->values(where));
1897 }
1898 
1899 ProStringList
fixListForOutput(const ProStringList & l)1900 ProjectBuilderMakefileGenerator::fixListForOutput(const ProStringList &l)
1901 {
1902     ProStringList ret;
1903     for(int i = 0; i < l.count(); i++)
1904         ret += fixForOutput(l[i].toQString());
1905     return ret;
1906 }
1907 
1908 QString
keyFor(const QString & block)1909 ProjectBuilderMakefileGenerator::keyFor(const QString &block)
1910 {
1911 #if 1 //This make this code much easier to debug..
1912     if(project->isActiveConfig("no_pb_munge_key"))
1913        return block;
1914 #endif
1915     QString ret;
1916     if(!keys.contains(block)) {
1917         ret = qtSha1(block.toUtf8()).left(24).toUpper();
1918         keys.insert(block, ret);
1919     } else {
1920         ret = keys[block];
1921     }
1922     return ret;
1923 }
1924 
1925 bool
openOutput(QFile & file,const QString & build) const1926 ProjectBuilderMakefileGenerator::openOutput(QFile &file, const QString &build) const
1927 {
1928     Q_ASSERT_X(QDir::isRelativePath(file.fileName()), "ProjectBuilderMakefileGenerator",
1929         "runQMake() should have normalized the filename and made it relative");
1930 
1931     QFileInfo fi(fileInfo(file.fileName()));
1932     if (fi.suffix() != "pbxproj") {
1933         QString output = file.fileName();
1934         if (!output.endsWith(projectSuffix())) {
1935             if (fi.fileName().isEmpty()) {
1936                 if (project->first("TEMPLATE") == "subdirs" || project->isEmpty("QMAKE_ORIG_TARGET"))
1937                     output += fileInfo(project->projectFile()).baseName();
1938                 else
1939                     output += project->first("QMAKE_ORIG_TARGET").toQString();
1940             }
1941             output += projectSuffix() + QDir::separator();
1942         } else {
1943             output += QDir::separator();
1944         }
1945         output += QString("project.pbxproj");
1946         file.setFileName(output);
1947     }
1948 
1949     pbx_dir = Option::output_dir + Option::dir_sep + file.fileName().section(Option::dir_sep, 0, 0);
1950     return UnixMakefileGenerator::openOutput(file, build);
1951 }
1952 
1953 int
pbuilderVersion() const1954 ProjectBuilderMakefileGenerator::pbuilderVersion() const
1955 {
1956     if (!project->isEmpty("QMAKE_PBUILDER_VERSION"))
1957         return project->first("QMAKE_PBUILDER_VERSION").toInt();
1958     return 46; // Xcode 3.2-compatible; default format since that version
1959 }
1960 
1961 int
reftypeForFile(const QString & where)1962 ProjectBuilderMakefileGenerator::reftypeForFile(const QString &where)
1963 {
1964     int ret = 0; //absolute is the default..
1965     if (QDir::isRelativePath(where))
1966         ret = 4; //relative
1967     return ret;
1968 }
1969 
1970 QString
projectSuffix() const1971 ProjectBuilderMakefileGenerator::projectSuffix() const
1972 {
1973     return ".xcodeproj";
1974 }
1975 
1976 QString
pbxbuild()1977 ProjectBuilderMakefileGenerator::pbxbuild()
1978 {
1979     return "xcodebuild";
1980 }
1981 
quotedStringLiteral(const QString & value)1982 static QString quotedStringLiteral(const QString &value)
1983 {
1984     QString result;
1985     const int len = value.length();
1986     result.reserve(int(len * 1.1) + 2);
1987 
1988     result += QLatin1Char('"');
1989 
1990     // Escape
1991     for (int i = 0; i < len; ++i) {
1992         QChar character = value.at(i);;
1993         ushort code = character.unicode();
1994         switch (code) {
1995         case '\\':
1996             result += QLatin1String("\\\\");
1997             break;
1998         case '"':
1999             result += QLatin1String("\\\"");
2000             break;
2001         case '\b':
2002             result += QLatin1String("\\b");
2003             break;
2004         case '\n':
2005             result += QLatin1String("\\n");
2006             break;
2007         case '\r':
2008             result += QLatin1String("\\r");
2009             break;
2010         case '\t':
2011             result += QLatin1String("\\t");
2012             break;
2013         default:
2014             if (code >= 32 && code <= 127)
2015                 result += character;
2016             else
2017                 result += QLatin1String("\\u") + QString::number(code, 16).rightJustified(4, '0');
2018         }
2019     }
2020 
2021     result += QLatin1Char('"');
2022 
2023     result.squeeze();
2024     return result;
2025 }
2026 
2027 QString
writeSettings(const QString & var,const ProStringList & vals,int flags,int indent_level)2028 ProjectBuilderMakefileGenerator::writeSettings(const QString &var, const ProStringList &vals, int flags, int indent_level)
2029 {
2030     QString ret;
2031     bool shouldQuote = !((flags & SettingsNoQuote));
2032 
2033     QString newline = "\n";
2034     for(int i = 0; i < indent_level; ++i)
2035         newline += "\t";
2036 
2037     static QRegExp allowedVariableCharacters("^[a-zA-Z0-9_]*$");
2038     ret += var.contains(allowedVariableCharacters) ? var : quotedStringLiteral(var);
2039 
2040     ret += " = ";
2041 
2042     if(flags & SettingsAsList) {
2043         ret += "(" + newline;
2044         for(int i = 0, count = 0; i < vals.size(); ++i) {
2045             QString val = vals.at(i).toQString();
2046             if(!val.isEmpty()) {
2047                 if(count++ > 0)
2048                     ret += "," + newline;
2049                 if (shouldQuote)
2050                     val = quotedStringLiteral(val);
2051                 ret += val;
2052             }
2053         }
2054         ret += ")";
2055     } else {
2056         QString val = vals.join(QLatin1Char(' '));
2057         if (shouldQuote)
2058             val = quotedStringLiteral(val);
2059         ret += val;
2060     }
2061     return ret;
2062 }
2063 
2064 QT_END_NAMESPACE
2065