iosprobe.cpp 14 KB
Newer Older
1
2
/****************************************************************************
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
5
6
7
8
9
10
11
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
16
17
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
27
28
29
30
31
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "iosprobe.h"
Eike Ziller's avatar
Eike Ziller committed
32

33
#include <QDir>
34
#include <QFileInfo>
35
#include <QFileInfoList>
36
37
38
39
#include <QLoggingCategory>
#include <QProcess>

static Q_LOGGING_CATEGORY(probeLog, "qtc.ios.probe")
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

namespace Ios {

static QString qsystem(const QString &exe, const QStringList &args = QStringList())
{
    QProcess p;
    p.setProcessChannelMode(QProcess::MergedChannels);
    p.start(exe, args);
    p.waitForFinished();
    return QString::fromLocal8Bit(p.readAll());
}

QMap<QString, Platform> IosProbe::detectPlatforms(const QString &devPath)
{
    IosProbe probe;
    probe.addDeveloperPath(devPath);
56
    probe.detectFirst();
57
58
59
    return probe.detectedPlatforms();
}

Eike Ziller's avatar
Eike Ziller committed
60
static int compareVersions(const QString &v1, const QString &v2)
61
62
63
64
{
    QStringList v1L = v1.split(QLatin1Char('.'));
    QStringList v2L = v2.split(QLatin1Char('.'));
    int i = 0;
Fawzi Mohamed's avatar
Fawzi Mohamed committed
65
    while (v1L.length() > i && v2L.length() > i) {
66
67
68
69
        bool n1Ok, n2Ok;
        int n1 = v1L.value(i).toInt(&n1Ok);
        int n2 = v2L.value(i).toInt(&n2Ok);
        if (!(n1Ok && n2Ok)) {
70
            qCWarning(probeLog) << QString::fromLatin1("Failed to compare version %1 and %2").arg(v1, v2);
71
72
73
74
75
76
77
78
            return 0;
        }
        if (n1 > n2)
            return -1;
        else if (n1 < n2)
            return 1;
        ++i;
    }
Fawzi Mohamed's avatar
Fawzi Mohamed committed
79
    if (v1L.length() > v2L.length())
80
        return -1;
Fawzi Mohamed's avatar
Fawzi Mohamed committed
81
    if (v1L.length() < v2L.length())
82
83
84
85
        return 1;
    return 0;
}

Eike Ziller's avatar
Eike Ziller committed
86
void IosProbe::addDeveloperPath(const QString &path)
87
88
{
    if (path.isEmpty())
Eike Ziller's avatar
Eike Ziller committed
89
        return;
90
91
    QFileInfo pInfo(path);
    if (!pInfo.exists() || !pInfo.isDir())
Eike Ziller's avatar
Eike Ziller committed
92
        return;
93
    if (m_developerPaths.contains(path))
Eike Ziller's avatar
Eike Ziller committed
94
        return;
95
    m_developerPaths.append(path);
96
    qCDebug(probeLog) << QString::fromLatin1("Added developer path %1").arg(path);
97
98
99
100
101
102
103
104
105
}

void IosProbe::detectDeveloperPaths()
{
    QProcess selectedXcode;
    QString program = QLatin1String("/usr/bin/xcode-select");
    QStringList arguments(QLatin1String("--print-path"));
    selectedXcode.start(program, arguments, QProcess::ReadOnly);
    if (!selectedXcode.waitForFinished() || selectedXcode.exitCode()) {
106
        qCWarning(probeLog) << QString::fromLatin1("Could not detect selected xcode with /usr/bin/xcode-select");
107
108
    } else {
        QString path = QString::fromLocal8Bit(selectedXcode.readAllStandardOutput());
Fawzi Mohamed's avatar
Fawzi Mohamed committed
109
        path.chop(1);
110
111
112
113
114
        addDeveloperPath(path);
    }
    addDeveloperPath(QLatin1String("/Applications/Xcode.app/Contents/Developer"));
}

Eike Ziller's avatar
Eike Ziller committed
115
void IosProbe::setupDefaultToolchains(const QString &devPath, const QString &xcodeName)
116
{
117
    qCDebug(probeLog) << QString::fromLatin1("Setting up platform \"%1\".").arg(xcodeName);
118
119
120
121
122
123
124
    QString indent = QLatin1String("  ");

    // detect clang (default toolchain)
    QFileInfo clangFileInfo(devPath
                            + QLatin1String("/Toolchains/XcodeDefault.xctoolchain/usr/bin")
                            + QLatin1String("/clang++"));
    bool hasClang = clangFileInfo.exists();
Eike Ziller's avatar
Eike Ziller committed
125
    if (!hasClang)
126
127
        qCWarning(probeLog) << indent << QString::fromLatin1("Default toolchain %1 not found.")
                                .arg(clangFileInfo.canonicalFilePath());
128
129
130
131
132
    // Platforms
    QDir platformsDir(devPath + QLatin1String("/Platforms"));
    QFileInfoList platforms = platformsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
    foreach (const QFileInfo &fInfo, platforms) {
        if (fInfo.isDir() && fInfo.suffix() == QLatin1String("platform")) {
133
            qCDebug(probeLog) << indent << QString::fromLatin1("Setting up %1").arg(fInfo.fileName());
134
135
136
            QSettings infoSettings(fInfo.absoluteFilePath() + QLatin1String("/Info.plist"),
                                   QSettings::NativeFormat);
            if (!infoSettings.contains(QLatin1String("Name"))) {
137
                qCWarning(probeLog) << indent << QString::fromLatin1("Missing platform name in Info.plist of %1")
138
139
140
                             .arg(fInfo.absoluteFilePath());
                continue;
            }
141
            QString name = infoSettings.value(QLatin1String("Name")).toString();
142
143
144
            if (name != QLatin1String("macosx") && name != QLatin1String("iphoneos")
                    && name != QLatin1String("iphonesimulator"))
            {
145
                qCWarning(probeLog) << indent << QString::fromLatin1("Skipping unknown platform %1").arg(name);
146
147
148
                continue;
            }

149
150
            const QString platformSdkVersion = infoSettings.value(QLatin1String("Version")).toString();

151
            // prepare default platform properties
152
            QVariantMap defaultProp = infoSettings.value(QLatin1String("DefaultProperties"))
153
                    .toMap();
154
            QVariantMap overrideProp = infoSettings.value(QLatin1String("OverrideProperties"))
155
156
157
158
159
160
161
162
                    .toMap();
            QMapIterator<QString, QVariant> i(overrideProp);
            while (i.hasNext()) {
                i.next();
                // use unite? might lead to double insertions...
                defaultProp[i.key()] = i.value();
            }

Eike Ziller's avatar
Eike Ziller committed
163
164
            QString clangFullName = name + QLatin1String("-clang") + xcodeName;
            QString clang11FullName = name + QLatin1String("-clang11") + xcodeName;
165
166
            // detect gcc
            QFileInfo gccFileInfo(fInfo.absoluteFilePath() + QLatin1String("/Developer/usr/bin/g++"));
Eike Ziller's avatar
Eike Ziller committed
167
            QString gccFullName = name + QLatin1String("-gcc") + xcodeName;
168
169
170
171
172
173
174
175
            if (!gccFileInfo.exists())
                gccFileInfo = QFileInfo(devPath + QLatin1String("/usr/bin/g++"));
            bool hasGcc = gccFileInfo.exists();

            QStringList extraFlags;
            if (defaultProp.contains(QLatin1String("NATIVE_ARCH"))) {
                QString arch = defaultProp.value(QLatin1String("NATIVE_ARCH")).toString();
                if (!arch.startsWith(QLatin1String("arm")))
176
                    qCWarning(probeLog) << indent << QString::fromLatin1("Expected arm architecture, not %1").arg(arch);
177
                extraFlags << QLatin1String("-arch") << arch;
178
179
180
            } else if (name == QLatin1String("iphonesimulator")) {
                // don't generate a toolchain for 64 bit (to fix when we support that)
                extraFlags << QLatin1String("-arch") << QLatin1String("i386");
181
182
183
            }
            if (hasClang) {
                Platform clangProfile;
Fawzi Mohamed's avatar
Fawzi Mohamed committed
184
                clangProfile.developerPath = Utils::FileName::fromString(devPath);
185
186
187
188
                clangProfile.platformKind = 0;
                clangProfile.name = clangFullName;
                clangProfile.platformPath = Utils::FileName(fInfo);
                clangProfile.compilerPath = Utils::FileName(clangFileInfo);
189
190
191
192
193
194
195
                QStringList flags = extraFlags;
                flags << QLatin1String("-dumpmachine");
                QString compilerTriplet = qsystem(clangFileInfo.canonicalFilePath(), flags)
                        .simplified();
                QStringList compilerTripletl = compilerTriplet.split(QLatin1Char('-'));
                clangProfile.architecture = compilerTripletl.value(0);
                clangProfile.backendFlags = extraFlags;
196
                qCDebug(probeLog) << indent << QString::fromLatin1("* adding profile %1").arg(clangProfile.name);
Eike Ziller's avatar
Eike Ziller committed
197
                m_platforms[clangProfile.name] = clangProfile;
198
199
200
201
                clangProfile.platformKind |= Platform::Cxx11Support;
                clangProfile.backendFlags.append(QLatin1String("-std=c++11"));
                clangProfile.backendFlags.append(QLatin1String("-stdlib=libc++"));
                clangProfile.name = clang11FullName;
Eike Ziller's avatar
Eike Ziller committed
202
                m_platforms[clangProfile.name] = clangProfile;
203
204
205
            }
            if (hasGcc) {
                Platform gccProfile;
Fawzi Mohamed's avatar
Fawzi Mohamed committed
206
                gccProfile.developerPath = Utils::FileName::fromString(devPath);
207
208
209
210
211
                gccProfile.name = gccFullName;
                gccProfile.platformKind = 0;
                // use the arm-apple-darwin10-llvm-* variant and avoid the extraFlags if available???
                gccProfile.platformPath = Utils::FileName(fInfo);
                gccProfile.compilerPath = Utils::FileName(gccFileInfo);
212
213
214
215
216
217
218
                QStringList flags = extraFlags;
                flags << QLatin1String("-dumpmachine");
                QString compilerTriplet = qsystem(gccFileInfo.canonicalFilePath(), flags)
                        .simplified();
                QStringList compilerTripletl = compilerTriplet.split(QLatin1Char('-'));
                gccProfile.architecture = compilerTripletl.value(0);
                gccProfile.backendFlags = extraFlags;
219
                qCDebug(probeLog) << indent << QString::fromLatin1("* adding profile %1").arg(gccProfile.name);
Eike Ziller's avatar
Eike Ziller committed
220
                m_platforms[gccProfile.name] = gccProfile;
221
222
223
224
225
226
227
228
229
            }

            // set SDKs/sysroot
            QString sysRoot;
            {
                QString sdkName;
                if (defaultProp.contains(QLatin1String("SDKROOT")))
                    sdkName = defaultProp.value(QLatin1String("SDKROOT")).toString();
                QString sdkPath;
230
                QString sdkPathWithSameVersion;
231
232
233
234
235
                QDir sdks(fInfo.absoluteFilePath() + QLatin1String("/Developer/SDKs"));
                QString maxVersion;
                foreach (const QFileInfo &sdkDirInfo, sdks.entryInfoList(QDir::Dirs
                                                                         | QDir::NoDotAndDotDot)) {
                    indent = QLatin1String("    ");
236
237
238
239
240
241
                    QSettings sdkInfo(sdkDirInfo.absoluteFilePath()
                                      + QLatin1String("/SDKSettings.plist"),
                                      QSettings::NativeFormat);
                    QString versionStr = sdkInfo.value(QLatin1String("Version")).toString();
                    QVariant currentSdkName = sdkInfo.value(QLatin1String("CanonicalName"));
                    bool isBaseSdk = sdkInfo.value((QLatin1String("isBaseSDK"))).toString()
242
243
                            .toLower() != QLatin1String("no");
                    if (!isBaseSdk) {
244
245
                        qCDebug(probeLog) << indent << QString::fromLatin1("Skipping non base Sdk %1")
                                                .arg(currentSdkName.toString());
246
247
248
                        continue;
                    }
                    if (sdkName.isEmpty()) {
249
                        if (maxVersion.isEmpty() || compareVersions(maxVersion, versionStr) > 0) {
250
251
252
253
254
                            maxVersion = versionStr;
                            sdkPath = sdkDirInfo.canonicalFilePath();
                        }
                    } else if (currentSdkName == sdkName) {
                        sdkPath = sdkDirInfo.canonicalFilePath();
255
256
257
                    } else if (currentSdkName.toString().startsWith(sdkName) /*if sdkName doesn't contain version*/
                            && compareVersions(platformSdkVersion, versionStr) == 0)
                        sdkPathWithSameVersion = sdkDirInfo.canonicalFilePath();
258
                }
259
260
261
                if (sdkPath.isEmpty())
                    sysRoot = sdkPathWithSameVersion;
                else
262
                    sysRoot = sdkPath;
263
                if (sysRoot.isEmpty() && !sdkName.isEmpty())
264
                    qCDebug(probeLog) << indent << QString::fromLatin1("Failed to find sysroot %1").arg(sdkName);
265
266
            }
            if (hasClang && !sysRoot.isEmpty()) {
Eike Ziller's avatar
Eike Ziller committed
267
268
269
270
                m_platforms[clangFullName].platformKind |= Platform::BasePlatform;
                m_platforms[clangFullName].sdkPath = Utils::FileName::fromString(sysRoot);
                m_platforms[clang11FullName].platformKind |= Platform::BasePlatform;
                m_platforms[clang11FullName].sdkPath = Utils::FileName::fromString(sysRoot);
271
272
            }
            if (hasGcc && !sysRoot.isEmpty()) {
Eike Ziller's avatar
Eike Ziller committed
273
274
                m_platforms[gccFullName].platformKind |= Platform::BasePlatform;
                m_platforms[gccFullName].sdkPath = Utils::FileName::fromString(sysRoot);
275
276
277
278
279
280
            }
        }
        indent = QLatin1String("  ");
    }
}

281
void IosProbe::detectFirst()
282
283
{
    detectDeveloperPaths();
284
285
    if (!m_developerPaths.isEmpty())
        setupDefaultToolchains(m_developerPaths.value(0),QLatin1String(""));
286
287
288
289
290
291
292
}

QMap<QString, Platform> IosProbe::detectedPlatforms()
{
    return m_platforms;
}

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
QDebug operator<<(QDebug debug, const Platform &platform)
{
    QDebugStateSaver saver(debug); Q_UNUSED(saver)
    debug.nospace() << "(name=" << platform.name
                    << ", compiler=" << platform.compilerPath.toString()
                    << ", flags=" << platform.backendFlags
                    << ")";
    return debug;
}

bool Platform::operator==(const Platform &other) const
{
    return name == other.name;
}

uint qHash(const Platform &platform)
{
    return qHash(platform.name);
}

313
}