1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "iosprobe.h"
27 
28 #include <utils/algorithm.h>
29 #include <utils/qtcprocess.h>
30 
31 #include <QDir>
32 #include <QFileInfo>
33 #include <QFileInfoList>
34 #include <QLoggingCategory>
35 #include <QProcess>
36 
37 static Q_LOGGING_CATEGORY(probeLog, "qtc.ios.probe", QtWarningMsg)
38 
39 using namespace Utils;
40 
41 namespace Ios {
42 
43 static QString defaultDeveloperPath = QLatin1String("/Applications/Xcode.app/Contents/Developer");
44 
detectPlatforms(const QString & devPath)45 QMap<QString, XcodePlatform> XcodeProbe::detectPlatforms(const QString &devPath)
46 {
47     XcodeProbe probe;
48     probe.addDeveloperPath(devPath);
49     probe.detectFirst();
50     return probe.detectedPlatforms();
51 }
52 
addDeveloperPath(const QString & path)53 void XcodeProbe::addDeveloperPath(const QString &path)
54 {
55     if (path.isEmpty())
56         return;
57     QFileInfo pInfo(path);
58     if (!pInfo.exists() || !pInfo.isDir())
59         return;
60     if (m_developerPaths.contains(path))
61         return;
62     m_developerPaths.append(path);
63     qCDebug(probeLog) << QString::fromLatin1("Added developer path %1").arg(path);
64 }
65 
detectDeveloperPaths()66 void XcodeProbe::detectDeveloperPaths()
67 {
68     Utils::QtcProcess selectedXcode;
69     selectedXcode.setTimeoutS(5);
70     selectedXcode.setCommand({"/usr/bin/xcode-select", {"--print-path"}});
71     selectedXcode.runBlocking();
72     if (selectedXcode.result() != QtcProcess::FinishedWithSuccess)
73         qCWarning(probeLog)
74                 << QString::fromLatin1("Could not detect selected Xcode using xcode-select");
75     else
76         addDeveloperPath(selectedXcode.stdOut().trimmed());
77     addDeveloperPath(defaultDeveloperPath);
78 }
79 
setupDefaultToolchains(const QString & devPath)80 void XcodeProbe::setupDefaultToolchains(const QString &devPath)
81 {
82     auto getClangInfo = [devPath](const QString &compiler) {
83         QFileInfo compilerInfo(devPath
84                                 + QLatin1String("/Toolchains/XcodeDefault.xctoolchain/usr/bin/")
85                                 + compiler);
86         if (!compilerInfo.exists())
87             qCWarning(probeLog) << QString::fromLatin1("Default toolchain %1 not found.")
88                                     .arg(compilerInfo.canonicalFilePath());
89         return compilerInfo;
90     };
91 
92     XcodePlatform clangProfile;
93     clangProfile.developerPath = Utils::FilePath::fromString(devPath);
94 
95     const QFileInfo clangCInfo = getClangInfo("clang");
96     if (clangCInfo.exists())
97         clangProfile.cCompilerPath = Utils::FilePath::fromFileInfo(clangCInfo);
98 
99     const QFileInfo clangCppInfo = getClangInfo("clang++");
100     if (clangCppInfo.exists())
101         clangProfile.cxxCompilerPath = Utils::FilePath::fromFileInfo(clangCppInfo);
102 
103     QSet<QString> allArchitectures;
104     static const std::map<QString, QStringList> sdkConfigs {
105         {QLatin1String("AppleTVOS"), QStringList("arm64")},
106         {QLatin1String("AppleTVSimulator"), QStringList("x86_64")},
107         {QLatin1String("iPhoneOS"), QStringList { QLatin1String("arm64"), QLatin1String("armv7") }},
108         {QLatin1String("iPhoneSimulator"), QStringList { QLatin1String("x86_64"),
109                         QLatin1String("i386") }},
110         {QLatin1String("MacOSX"), QStringList { QLatin1String("x86_64"), QLatin1String("i386") }},
111         {QLatin1String("WatchOS"), QStringList("armv7k")},
112         {QLatin1String("WatchSimulator"), QStringList("i386")}
113     };
114     for (const auto &sdkConfig : sdkConfigs) {
115         XcodePlatform::SDK sdk;
116         sdk.directoryName = sdkConfig.first;
117         sdk.path = Utils::FilePath::fromString(devPath
118                 + QString(QLatin1String("/Platforms/%1.platform/Developer/SDKs/%1.sdk")).arg(
119                     sdk.directoryName));
120         sdk.architectures = sdkConfig.second;
121         const QFileInfo sdkPathInfo(sdk.path.toString());
122         if (sdkPathInfo.exists() && sdkPathInfo.isDir()) {
123             clangProfile.sdks.push_back(sdk);
124             allArchitectures += Utils::toSet(sdk.architectures);
125         }
126     }
127 
128     if (!clangProfile.cCompilerPath.isEmpty() || !clangProfile.cxxCompilerPath.isEmpty()) {
129         for (const QString &arch : qAsConst(allArchitectures)) {
130             const QString clangFullName = QString(QLatin1String("Apple Clang (%1)")).arg(arch)
131                     + ((devPath != defaultDeveloperPath)
132                        ? QString(QLatin1String(" in %1")).arg(devPath)
133                        : QString());
134 
135             XcodePlatform::ToolchainTarget target;
136             target.name = clangFullName;
137             target.architecture = arch;
138             target.backendFlags = QStringList { QLatin1String("-arch"), arch };
139             clangProfile.targets.push_back(target);
140         }
141     }
142 
143     m_platforms[devPath] = clangProfile;
144 }
145 
detectFirst()146 void XcodeProbe::detectFirst()
147 {
148     detectDeveloperPaths();
149     if (!m_developerPaths.isEmpty())
150         setupDefaultToolchains(m_developerPaths.first());
151 }
152 
detectedPlatforms()153 QMap<QString, XcodePlatform> XcodeProbe::detectedPlatforms()
154 {
155     return m_platforms;
156 }
157 
operator ==(const XcodePlatform & other) const158 bool XcodePlatform::operator==(const XcodePlatform &other) const
159 {
160     return developerPath == other.developerPath;
161 }
162 
qHash(const XcodePlatform & platform)163 uint qHash(const XcodePlatform &platform)
164 {
165     return qHash(platform.developerPath);
166 }
167 
qHash(const XcodePlatform::ToolchainTarget & target)168 uint qHash(const XcodePlatform::ToolchainTarget &target)
169 {
170     return qHash(target.name);
171 }
172 
operator ==(const XcodePlatform::ToolchainTarget & other) const173 bool XcodePlatform::ToolchainTarget::operator==(const XcodePlatform::ToolchainTarget &other) const
174 {
175     return architecture == other.architecture;
176 }
177 
178 }
179