Refactor iOS toolchain handling

This significantly simplifies the implementation, properly handles
C vs C++ compilers, and defaults to 64-bit architectures for iOS devices
and simulators.

LLVM-GCC support has been removed, as the last version to support it was
4.6.3, which is far below the version of Xcode we still intend to
support. The toolchains are also given more sensible names
("Apple Clang") instead of iphonesimulator-clang and iphoneos-clang, as
the there is now only one compiler for all Apple platform targets, and
there is one entry of the Apple Clang compiler per supported
architecture.

The different to the end user is minimal except that the compilers are
named better. The number of kits and their configurations remain the
same, but this patch paves the way for tvOS and watchOS support.

Change-Id: I6e2b17f8c17d9dd504f0ad540e08782f291b73c0
Reviewed-by: Vikas Pachdha <vikas.pachdha@qt.io>
This commit is contained in:
Jake Petroules
2017-05-24 23:26:35 -07:00
parent 232f6a1096
commit 0784dd20fe
3 changed files with 203 additions and 386 deletions

View File

@@ -37,50 +37,17 @@ static Q_LOGGING_CATEGORY(probeLog, "qtc.ios.probe")
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());
}
static QString defaultDeveloperPath = QLatin1String("/Applications/Xcode.app/Contents/Developer");
QMap<QString, Platform> IosProbe::detectPlatforms(const QString &devPath)
QMap<QString, XcodePlatform> XcodeProbe::detectPlatforms(const QString &devPath)
{
IosProbe probe;
XcodeProbe probe;
probe.addDeveloperPath(devPath);
probe.detectFirst();
return probe.detectedPlatforms();
}
static int compareVersions(const QString &v1, const QString &v2)
{
QStringList v1L = v1.split(QLatin1Char('.'));
QStringList v2L = v2.split(QLatin1Char('.'));
int i = 0;
while (v1L.length() > i && v2L.length() > i) {
bool n1Ok, n2Ok;
int n1 = v1L.value(i).toInt(&n1Ok);
int n2 = v2L.value(i).toInt(&n2Ok);
if (!(n1Ok && n2Ok)) {
qCWarning(probeLog) << QString::fromLatin1("Failed to compare version %1 and %2").arg(v1, v2);
return 0;
}
if (n1 > n2)
return -1;
else if (n1 < n2)
return 1;
++i;
}
if (v1L.length() > v2L.length())
return -1;
if (v1L.length() < v2L.length())
return 1;
return 0;
}
void IosProbe::addDeveloperPath(const QString &path)
void XcodeProbe::addDeveloperPath(const QString &path)
{
if (path.isEmpty())
return;
@@ -93,252 +60,116 @@ void IosProbe::addDeveloperPath(const QString &path)
qCDebug(probeLog) << QString::fromLatin1("Added developer path %1").arg(path);
}
void IosProbe::detectDeveloperPaths()
void XcodeProbe::detectDeveloperPaths()
{
QString program = QLatin1String("/usr/bin/xcode-select");
QStringList arguments(QLatin1String("--print-path"));
Utils::SynchronousProcess selectedXcode;
selectedXcode.setTimeoutS(5);
Utils::SynchronousProcessResponse response = selectedXcode.run(program, arguments);
if (response.result != Utils::SynchronousProcessResponse::Finished) {
qCWarning(probeLog) << QString::fromLatin1("Could not detect selected xcode with /usr/bin/xcode-select");
} else {
QString path = response.stdOut();
path.chop(1);
addDeveloperPath(path);
}
addDeveloperPath(QLatin1String("/Applications/Xcode.app/Contents/Developer"));
Utils::SynchronousProcessResponse response = selectedXcode.run(
QLatin1String("/usr/bin/xcode-select"), QStringList("--print-path"));
if (response.result != Utils::SynchronousProcessResponse::Finished)
qCWarning(probeLog)
<< QString::fromLatin1("Could not detect selected Xcode using xcode-select");
else
addDeveloperPath(response.stdOut().trimmed());
addDeveloperPath(defaultDeveloperPath);
}
void IosProbe::setupDefaultToolchains(const QString &devPath, const QString &xcodeName)
void XcodeProbe::setupDefaultToolchains(const QString &devPath)
{
qCDebug(probeLog) << QString::fromLatin1("Setting up platform \"%1\".").arg(xcodeName);
QString indent = QLatin1String(" ");
auto getClangInfo = [devPath, indent](const QString &compiler) {
auto getClangInfo = [devPath](const QString &compiler) {
QFileInfo compilerInfo(devPath
+ QLatin1String("/Toolchains/XcodeDefault.xctoolchain/usr/bin/")
+ compiler);
if (!compilerInfo.exists())
qCWarning(probeLog) << indent << QString::fromLatin1("Default toolchain %1 not found.")
qCWarning(probeLog) << QString::fromLatin1("Default toolchain %1 not found.")
.arg(compilerInfo.canonicalFilePath());
return compilerInfo;
};
// detect clang (default toolchain)
const QFileInfo clangCppInfo = getClangInfo("clang++");
const bool hasClangCpp = clangCppInfo.exists();
XcodePlatform clangProfile;
clangProfile.developerPath = Utils::FileName::fromString(devPath);
const QFileInfo clangCInfo = getClangInfo("clang");
const bool hasClangC = clangCInfo.exists();
if (clangCInfo.exists())
clangProfile.cCompilerPath = Utils::FileName(clangCInfo);
// Platforms
const QDir platformsDir(devPath + QLatin1String("/Platforms"));
const QFileInfoList platforms = platformsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QFileInfo &fInfo, platforms) {
if (fInfo.isDir() && fInfo.suffix() == QLatin1String("platform")) {
qCDebug(probeLog) << indent << QString::fromLatin1("Setting up %1").arg(fInfo.fileName());
QSettings infoSettings(fInfo.absoluteFilePath() + QLatin1String("/Info.plist"),
QSettings::NativeFormat);
if (!infoSettings.contains(QLatin1String("Name"))) {
qCWarning(probeLog) << indent << QString::fromLatin1("Missing platform name in Info.plist of %1")
.arg(fInfo.absoluteFilePath());
continue;
}
QString name = infoSettings.value(QLatin1String("Name")).toString();
if (name != QLatin1String("macosx") && name != QLatin1String("iphoneos")
&& name != QLatin1String("iphonesimulator"))
{
qCDebug(probeLog) << indent << QString::fromLatin1("Skipping unknown platform %1").arg(name);
continue;
}
const QFileInfo clangCppInfo = getClangInfo("clang++");
if (clangCppInfo.exists())
clangProfile.cxxCompilerPath = Utils::FileName(clangCppInfo);
const QString platformSdkVersion = infoSettings.value(QLatin1String("Version")).toString();
// prepare default platform properties
QVariantMap defaultProp = infoSettings.value(QLatin1String("DefaultProperties"))
.toMap();
QVariantMap overrideProp = infoSettings.value(QLatin1String("OverrideProperties"))
.toMap();
QMapIterator<QString, QVariant> i(overrideProp);
while (i.hasNext()) {
i.next();
// use unite? might lead to double insertions...
defaultProp[i.key()] = i.value();
}
const QString clangFullName = name + QLatin1String("-clang") + xcodeName;
const QString clang11FullName = name + QLatin1String("-clang11") + xcodeName;
// detect gcc
QFileInfo gccCppInfo(fInfo.absoluteFilePath() + QLatin1String("/Developer/usr/bin/g++"));
if (!gccCppInfo.exists())
gccCppInfo = QFileInfo(devPath + QLatin1String("/usr/bin/g++"));
const bool hasGccCppCompiler = gccCppInfo.exists();
QFileInfo gccCInfo(fInfo.absoluteFilePath() + QLatin1String("/Developer/usr/bin/gcc"));
if (!gccCInfo.exists())
gccCInfo = QFileInfo(devPath + QLatin1String("/usr/bin/gcc"));
const bool hasGccCCompiler = gccCInfo.exists();
const QString gccFullName = name + QLatin1String("-gcc") + xcodeName;
QStringList extraFlags;
if (defaultProp.contains(QLatin1String("NATIVE_ARCH"))) {
QString arch = defaultProp.value(QLatin1String("NATIVE_ARCH")).toString();
if (!arch.startsWith(QLatin1String("arm")))
qCWarning(probeLog) << indent << QString::fromLatin1("Expected arm architecture, not %1").arg(arch);
extraFlags << QLatin1String("-arch") << arch;
} else if (name == QLatin1String("iphonesimulator")) {
// don't generate a toolchain for 64 bit (to fix when we support that)
extraFlags << QLatin1String("-arch") << QLatin1String("i386");
}
auto getArch = [extraFlags](const QFileInfo &compiler) {
QStringList flags = extraFlags;
flags << QLatin1String("-dumpmachine");
const QStringList compilerTriplet = qsystem(compiler.canonicalFilePath(), flags)
.simplified().split(QLatin1Char('-'));
return compilerTriplet.value(0);
};
if (hasClangCpp || hasClangC) {
Platform clangProfile;
clangProfile.type = Platform::CLang;
clangProfile.developerPath = Utils::FileName::fromString(devPath);
clangProfile.platformKind = 0;
clangProfile.platformPath = Utils::FileName(fInfo);
clangProfile.architecture = getArch(hasClangCpp ? clangCppInfo : clangCInfo);
clangProfile.backendFlags = extraFlags;
qCDebug(probeLog) << indent << QString::fromLatin1("* adding profile %1").arg(clangProfile.name);
clangProfile.name = clangFullName;
if (hasClangC) {
clangProfile.cCompilerPath = Utils::FileName(clangCInfo);
m_platforms[clangFullName] = clangProfile;
}
if (hasClangCpp) {
clangProfile.cxxCompilerPath = Utils::FileName(clangCppInfo);
m_platforms[clangFullName] = clangProfile;
clangProfile.platformKind |= Platform::Cxx11Support;
clangProfile.backendFlags.append(QLatin1String("-std=c++11"));
clangProfile.backendFlags.append(QLatin1String("-stdlib=libc++"));
clangProfile.name = clang11FullName;
m_platforms[clang11FullName] = clangProfile;
}
}
if (hasGccCppCompiler || hasGccCCompiler) {
Platform gccProfile;
gccProfile.type = Platform::GCC;
gccProfile.developerPath = Utils::FileName::fromString(devPath);
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.architecture = getArch(hasGccCppCompiler ? gccCppInfo : gccCInfo);
gccProfile.backendFlags = extraFlags;
qCDebug(probeLog) << indent << QString::fromLatin1("* adding profile %1").arg(gccProfile.name);
if (hasGccCppCompiler) {
gccProfile.cxxCompilerPath = Utils::FileName(gccCppInfo);
}
if (hasGccCCompiler) {
gccProfile.cCompilerPath = Utils::FileName(gccCInfo);
}
m_platforms[gccFullName] = gccProfile;
}
// set SDKs/sysroot
QString sysRoot;
{
QString sdkName;
if (defaultProp.contains(QLatin1String("SDKROOT")))
sdkName = defaultProp.value(QLatin1String("SDKROOT")).toString();
QString sdkPath;
QString sdkPathWithSameVersion;
QDir sdks(fInfo.absoluteFilePath() + QLatin1String("/Developer/SDKs"));
QString maxVersion;
foreach (const QFileInfo &sdkDirInfo, sdks.entryInfoList(QDir::Dirs
| QDir::NoDotAndDotDot)) {
indent = QLatin1String(" ");
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()
.toLower() != QLatin1String("no");
if (!isBaseSdk) {
qCDebug(probeLog) << indent << QString::fromLatin1("Skipping non base Sdk %1")
.arg(currentSdkName.toString());
continue;
}
if (sdkName.isEmpty()) {
if (maxVersion.isEmpty() || compareVersions(maxVersion, versionStr) > 0) {
maxVersion = versionStr;
sdkPath = sdkDirInfo.canonicalFilePath();
}
} else if (currentSdkName == sdkName) {
sdkPath = sdkDirInfo.canonicalFilePath();
} else if (currentSdkName.toString().startsWith(sdkName) /*if sdkName doesn't contain version*/
&& compareVersions(platformSdkVersion, versionStr) == 0)
sdkPathWithSameVersion = sdkDirInfo.canonicalFilePath();
}
if (sdkPath.isEmpty())
sysRoot = sdkPathWithSameVersion;
else
sysRoot = sdkPath;
if (sysRoot.isEmpty() && !sdkName.isEmpty())
qCDebug(probeLog) << indent << QString::fromLatin1("Failed to find sysroot %1").arg(sdkName);
}
if (!sysRoot.isEmpty()) {
auto itr = m_platforms.begin();
while (itr != m_platforms.end()) {
if (itr.key() == clangFullName ||
itr.key() == clang11FullName ||
itr.key() == gccFullName) {
itr.value().platformKind |= Platform::BasePlatform;
itr.value().sdkPath = Utils::FileName::fromString(sysRoot);
}
++itr;
}
}
QSet<QString> allArchitectures;
static const std::map<QString, QStringList> sdkConfigs {
{QLatin1String("AppleTVOS"), QStringList("arm64")},
{QLatin1String("AppleTVSimulator"), QStringList("x86_64")},
{QLatin1String("iPhoneOS"), QStringList { QLatin1String("arm64"), QLatin1String("armv7") }},
{QLatin1String("iPhoneSimulator"), QStringList { QLatin1String("x86_64"),
QLatin1String("i386") }},
{QLatin1String("MacOSX"), QStringList { QLatin1String("x86_64"), QLatin1String("i386") }},
{QLatin1String("WatchOS"), QStringList("armv7k")},
{QLatin1String("WatchSimulator"), QStringList("i386")}
};
for (const auto &sdkConfig : sdkConfigs) {
XcodePlatform::SDK sdk;
sdk.directoryName = sdkConfig.first;
sdk.path = Utils::FileName::fromString(devPath
+ QString(QLatin1String("/Platforms/%1.platform/Developer/SDKs/%1.sdk")).arg(
sdk.directoryName));
sdk.architectures = sdkConfig.second;
const QFileInfo sdkPathInfo(sdk.path.toString());
if (sdkPathInfo.exists() && sdkPathInfo.isDir()) {
clangProfile.sdks.push_back(sdk);
allArchitectures += sdk.architectures.toSet();
}
indent = QLatin1String(" ");
}
if (!clangProfile.cCompilerPath.isEmpty() || !clangProfile.cxxCompilerPath.isEmpty()) {
for (const QString &arch : allArchitectures) {
const QString clangFullName = QString(QLatin1String("Apple Clang (%1)")).arg(arch)
+ ((devPath != defaultDeveloperPath)
? QString(QLatin1String(" in %1")).arg(devPath)
: QString());
XcodePlatform::ToolchainTarget target;
target.name = clangFullName;
target.architecture = arch;
target.backendFlags = QStringList { QLatin1String("-arch"), arch };
clangProfile.targets.push_back(target);
}
}
m_platforms[devPath] = clangProfile;
}
void IosProbe::detectFirst()
void XcodeProbe::detectFirst()
{
detectDeveloperPaths();
if (!m_developerPaths.isEmpty())
setupDefaultToolchains(m_developerPaths.value(0),QLatin1String(""));
setupDefaultToolchains(m_developerPaths.first());
}
QMap<QString, Platform> IosProbe::detectedPlatforms()
QMap<QString, XcodePlatform> XcodeProbe::detectedPlatforms()
{
return m_platforms;
}
QDebug operator<<(QDebug debug, const Platform &platform)
bool XcodePlatform::operator==(const XcodePlatform &other) const
{
QDebugStateSaver saver(debug); Q_UNUSED(saver)
debug.nospace() << "(name=" << platform.name
<< ", C++ compiler=" << platform.cxxCompilerPath.toString()
<< ", C compiler=" << platform.cCompilerPath.toString()
<< ", flags=" << platform.backendFlags
<< ")";
return debug;
return developerPath == other.developerPath;
}
bool Platform::operator==(const Platform &other) const
uint qHash(const XcodePlatform &platform)
{
return name == other.name;
return qHash(platform.developerPath);
}
uint qHash(const Platform &platform)
uint qHash(const XcodePlatform::ToolchainTarget &target)
{
return qHash(platform.name);
return qHash(target.name);
}
bool XcodePlatform::ToolchainTarget::operator==(const XcodePlatform::ToolchainTarget &other) const
{
return architecture == other.architecture;
}
}