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