2013-04-25 16:02:17 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2013-04-25 16:02:17 +02:00
|
|
|
**
|
|
|
|
|
** 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
|
2016-01-15 14:57:40 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2013-04-25 16:02:17 +02:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2013-04-25 16:02:17 +02:00
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "iosconfigurations.h"
|
|
|
|
|
#include "iosconstants.h"
|
|
|
|
|
#include "iosdevice.h"
|
|
|
|
|
#include "iossimulator.h"
|
2016-09-26 12:40:09 +02:00
|
|
|
#include "simulatorcontrol.h"
|
2013-04-25 16:02:17 +02:00
|
|
|
#include "iosprobe.h"
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
2015-10-09 09:50:07 +02:00
|
|
|
#include <utils/algorithm.h>
|
2017-03-03 16:20:53 +01:00
|
|
|
#include <utils/synchronousprocess.h>
|
2013-04-25 16:02:17 +02:00
|
|
|
#include <utils/qtcassert.h>
|
2016-12-21 10:13:38 +01:00
|
|
|
#include <utils/synchronousprocess.h>
|
2013-04-25 16:02:17 +02:00
|
|
|
#include <projectexplorer/kitmanager.h>
|
|
|
|
|
#include <projectexplorer/kitinformation.h>
|
|
|
|
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
|
|
|
|
#include <projectexplorer/toolchainmanager.h>
|
|
|
|
|
#include <projectexplorer/toolchain.h>
|
|
|
|
|
#include <projectexplorer/gcctoolchain.h>
|
|
|
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
2013-10-25 13:47:08 +02:00
|
|
|
#include <debugger/debuggeritemmanager.h>
|
2015-02-26 15:29:28 +01:00
|
|
|
#include <debugger/debuggeritem.h>
|
2013-04-25 16:02:17 +02:00
|
|
|
#include <debugger/debuggerkitinformation.h>
|
|
|
|
|
#include <qtsupport/baseqtversion.h>
|
|
|
|
|
#include <qtsupport/qtkitinformation.h>
|
|
|
|
|
#include <qtsupport/qtversionmanager.h>
|
|
|
|
|
#include <qtsupport/qtversionfactory.h>
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
#include <QDir>
|
2016-12-21 10:13:38 +01:00
|
|
|
#include <QDomDocument>
|
2013-04-25 16:02:17 +02:00
|
|
|
#include <QFileInfo>
|
2017-03-03 16:20:53 +01:00
|
|
|
#include <QFileSystemWatcher>
|
2015-10-09 09:50:07 +02:00
|
|
|
#include <QHash>
|
2013-04-25 16:02:17 +02:00
|
|
|
#include <QList>
|
2017-03-03 16:20:53 +01:00
|
|
|
#include <QLoggingCategory>
|
|
|
|
|
#include <QProcess>
|
2013-10-14 17:07:51 +02:00
|
|
|
#include <QSettings>
|
2017-01-27 11:00:20 +01:00
|
|
|
#include <QStringList>
|
|
|
|
|
#include <QStandardPaths>
|
2013-10-14 17:07:51 +02:00
|
|
|
#include <QTimer>
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2018-07-16 15:32:23 +02:00
|
|
|
#include <memory>
|
|
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
using namespace ProjectExplorer;
|
2013-10-14 17:07:51 +02:00
|
|
|
using namespace QtSupport;
|
2013-04-25 16:02:17 +02:00
|
|
|
using namespace Utils;
|
2013-11-05 11:32:47 +01:00
|
|
|
using namespace Debugger;
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2014-07-07 09:24:17 +02:00
|
|
|
namespace {
|
2018-10-12 09:33:30 +03:00
|
|
|
Q_LOGGING_CATEGORY(kitSetupLog, "qtc.ios.kitSetup", QtWarningMsg)
|
|
|
|
|
Q_LOGGING_CATEGORY(iosCommonLog, "qtc.ios.common", QtWarningMsg)
|
2014-07-07 09:24:17 +02:00
|
|
|
}
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2016-11-16 14:59:10 +01:00
|
|
|
using ToolChainPair = std::pair<ClangToolChain *, ClangToolChain *>;
|
2013-04-25 16:02:17 +02:00
|
|
|
namespace Ios {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2017-03-15 17:04:47 +01:00
|
|
|
const char SettingsGroup[] = "IosConfigurations";
|
|
|
|
|
const char ignoreAllDevicesKey[] = "IgnoreAllDevices";
|
2017-01-27 11:00:20 +01:00
|
|
|
const char screenshotDirPathKey[] = "ScreeshotDirPath";
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
const char provisioningTeamsTag[] = "IDEProvisioningTeams";
|
|
|
|
|
const char freeTeamTag[] = "isFreeProvisioningTeam";
|
|
|
|
|
const char emailTag[] = "eMail";
|
|
|
|
|
const char teamNameTag[] = "teamName";
|
|
|
|
|
const char teamIdTag[] = "teamID";
|
|
|
|
|
|
|
|
|
|
const char udidTag[] = "UUID";
|
|
|
|
|
const char profileNameTag[] = "Name";
|
|
|
|
|
const char appIdTag[] = "AppIDName";
|
|
|
|
|
const char expirationDateTag[] = "ExpirationDate";
|
|
|
|
|
const char profileTeamIdTag[] = "TeamIdentifier";
|
|
|
|
|
|
|
|
|
|
static const QString xcodePlistPath = QDir::homePath() + "/Library/Preferences/com.apple.dt.Xcode.plist";
|
|
|
|
|
static const QString provisioningProfileDirPath = QDir::homePath() + "/Library/MobileDevice/Provisioning Profiles";
|
|
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
static Core::Id deviceId(const QString &sdkName)
|
2013-04-25 16:02:17 +02:00
|
|
|
{
|
2017-03-15 17:04:47 +01:00
|
|
|
if (sdkName.startsWith("iphoneos", Qt::CaseInsensitive))
|
2015-10-09 09:50:07 +02:00
|
|
|
return Constants::IOS_DEVICE_TYPE;
|
2017-03-15 17:04:47 +01:00
|
|
|
else if (sdkName.startsWith("iphonesimulator", Qt::CaseInsensitive))
|
2015-10-09 09:50:07 +02:00
|
|
|
return Constants::IOS_SIMULATOR_TYPE;
|
2018-11-12 19:55:59 +01:00
|
|
|
return {};
|
2015-10-09 09:50:07 +02:00
|
|
|
}
|
2013-10-14 17:07:51 +02:00
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
static bool isSimulatorDeviceId(const Core::Id &id)
|
2015-10-09 09:50:07 +02:00
|
|
|
{
|
2017-05-24 23:26:35 -07:00
|
|
|
return id == Constants::IOS_SIMULATOR_TYPE;
|
2015-10-09 09:50:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QList<ClangToolChain *> clangToolChains(const QList<ToolChain *> &toolChains)
|
|
|
|
|
{
|
|
|
|
|
QList<ClangToolChain *> clangToolChains;
|
|
|
|
|
foreach (ToolChain *toolChain, toolChains)
|
|
|
|
|
if (toolChain->typeId() == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID)
|
|
|
|
|
clangToolChains.append(static_cast<ClangToolChain *>(toolChain));
|
|
|
|
|
return clangToolChains;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QList<ClangToolChain *> autoDetectedIosToolChains()
|
|
|
|
|
{
|
|
|
|
|
const QList<ClangToolChain *> toolChains = clangToolChains(ToolChainManager::toolChains());
|
|
|
|
|
return Utils::filtered(toolChains, [](ClangToolChain *toolChain) {
|
|
|
|
|
return toolChain->isAutoDetected()
|
2017-03-15 17:04:47 +01:00
|
|
|
&& (toolChain->displayName().startsWith("iphone")
|
|
|
|
|
|| toolChain->displayName().startsWith("Apple Clang")); // TODO tool chains should be marked directly
|
2015-10-09 09:50:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
static ToolChainPair findToolChainForPlatform(const XcodePlatform &platform,
|
|
|
|
|
const XcodePlatform::ToolchainTarget &target,
|
|
|
|
|
const QList<ClangToolChain *> &toolChains)
|
2015-10-09 09:50:07 +02:00
|
|
|
{
|
2016-11-16 14:59:10 +01:00
|
|
|
ToolChainPair platformToolChains;
|
|
|
|
|
auto toolchainMatch = [](ClangToolChain *toolChain, const Utils::FileName &compilerPath, const QStringList &flags) {
|
|
|
|
|
return compilerPath == toolChain->compilerCommand()
|
|
|
|
|
&& flags == toolChain->platformCodeGenFlags()
|
|
|
|
|
&& flags == toolChain->platformLinkerFlags();
|
|
|
|
|
};
|
|
|
|
|
platformToolChains.first = Utils::findOrDefault(toolChains, std::bind(toolchainMatch, std::placeholders::_1,
|
|
|
|
|
platform.cCompilerPath,
|
2017-05-24 23:26:35 -07:00
|
|
|
target.backendFlags));
|
2016-11-16 14:59:10 +01:00
|
|
|
platformToolChains.second = Utils::findOrDefault(toolChains, std::bind(toolchainMatch, std::placeholders::_1,
|
|
|
|
|
platform.cxxCompilerPath,
|
2017-05-24 23:26:35 -07:00
|
|
|
target.backendFlags));
|
2016-11-16 14:59:10 +01:00
|
|
|
return platformToolChains;
|
2015-10-09 09:50:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
static QHash<XcodePlatform::ToolchainTarget, ToolChainPair> findToolChains(const QList<XcodePlatform> &platforms)
|
2015-10-09 09:50:07 +02:00
|
|
|
{
|
2017-05-24 23:26:35 -07:00
|
|
|
QHash<XcodePlatform::ToolchainTarget, ToolChainPair> platformToolChainHash;
|
2015-10-09 09:50:07 +02:00
|
|
|
const QList<ClangToolChain *> toolChains = autoDetectedIosToolChains();
|
2017-05-24 23:26:35 -07:00
|
|
|
for (const XcodePlatform &platform : platforms) {
|
|
|
|
|
for (const XcodePlatform::ToolchainTarget &target : platform.targets) {
|
|
|
|
|
ToolChainPair platformToolchains = findToolChainForPlatform(platform, target,
|
|
|
|
|
toolChains);
|
|
|
|
|
if (platformToolchains.first || platformToolchains.second)
|
|
|
|
|
platformToolChainHash.insert(target, platformToolchains);
|
|
|
|
|
}
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
2015-10-09 09:50:07 +02:00
|
|
|
return platformToolChainHash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QSet<Kit *> existingAutoDetectedIosKits()
|
|
|
|
|
{
|
|
|
|
|
return Utils::filtered(KitManager::kits(), [](Kit *kit) -> bool {
|
|
|
|
|
Core::Id deviceKind = DeviceTypeKitInformation::deviceTypeId(kit);
|
|
|
|
|
return kit->isAutoDetected() && (deviceKind == Constants::IOS_DEVICE_TYPE
|
|
|
|
|
|| deviceKind == Constants::IOS_SIMULATOR_TYPE);
|
|
|
|
|
}).toSet();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void printKits(const QSet<Kit *> &kits)
|
|
|
|
|
{
|
|
|
|
|
foreach (const Kit *kit, kits)
|
|
|
|
|
qCDebug(kitSetupLog) << " -" << kit->displayName();
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-16 14:59:10 +01:00
|
|
|
static void setupKit(Kit *kit, Core::Id pDeviceType, const ToolChainPair& toolChains,
|
2015-10-09 09:50:07 +02:00
|
|
|
const QVariant &debuggerId, const Utils::FileName &sdkPath, BaseQtVersion *qtVersion)
|
|
|
|
|
{
|
|
|
|
|
DeviceTypeKitInformation::setDeviceTypeId(kit, pDeviceType);
|
2016-12-12 17:13:29 +01:00
|
|
|
if (toolChains.first)
|
|
|
|
|
ToolChainKitInformation::setToolChain(kit, toolChains.first);
|
|
|
|
|
else
|
2016-12-16 00:43:14 +01:00
|
|
|
ToolChainKitInformation::clearToolChain(kit, ProjectExplorer::Constants::C_LANGUAGE_ID);
|
2016-12-12 17:13:29 +01:00
|
|
|
if (toolChains.second)
|
|
|
|
|
ToolChainKitInformation::setToolChain(kit, toolChains.second);
|
|
|
|
|
else
|
2016-12-16 00:43:14 +01:00
|
|
|
ToolChainKitInformation::clearToolChain(kit, ProjectExplorer::Constants::CXX_LANGUAGE_ID);
|
2016-12-12 17:13:29 +01:00
|
|
|
|
2015-10-09 09:50:07 +02:00
|
|
|
QtKitInformation::setQtVersion(kit, qtVersion);
|
|
|
|
|
// only replace debugger with the default one if we find an unusable one here
|
|
|
|
|
// (since the user could have changed it)
|
|
|
|
|
if ((!DebuggerKitInformation::debugger(kit)
|
|
|
|
|
|| !DebuggerKitInformation::debugger(kit)->isValid()
|
|
|
|
|
|| DebuggerKitInformation::debugger(kit)->engineType() != LldbEngineType)
|
|
|
|
|
&& debuggerId.isValid())
|
|
|
|
|
DebuggerKitInformation::setDebugger(kit, debuggerId);
|
|
|
|
|
|
|
|
|
|
kit->setMutable(DeviceKitInformation::id(), true);
|
|
|
|
|
kit->setSticky(QtKitInformation::id(), true);
|
|
|
|
|
kit->setSticky(ToolChainKitInformation::id(), true);
|
|
|
|
|
kit->setSticky(DeviceTypeKitInformation::id(), true);
|
|
|
|
|
kit->setSticky(SysRootKitInformation::id(), true);
|
|
|
|
|
kit->setSticky(DebuggerKitInformation::id(), false);
|
|
|
|
|
|
|
|
|
|
SysRootKitInformation::setSysRoot(kit, sdkPath);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-10 13:01:06 +02:00
|
|
|
static QVersionNumber findXcodeVersion(const Utils::FileName &developerPath)
|
2016-12-21 10:13:38 +01:00
|
|
|
{
|
2017-10-10 13:01:06 +02:00
|
|
|
FileName xcodeInfo = developerPath.parentDir().appendPath("Info.plist");
|
|
|
|
|
if (xcodeInfo.exists()) {
|
|
|
|
|
QSettings settings(xcodeInfo.toString(), QSettings::NativeFormat);
|
|
|
|
|
return QVersionNumber::fromString(settings.value("CFBundleShortVersionString").toString());
|
2016-12-21 10:13:38 +01:00
|
|
|
} else {
|
2017-10-10 13:01:06 +02:00
|
|
|
qCDebug(iosCommonLog) << "Error finding Xcode version." << xcodeInfo.toUserOutput() <<
|
|
|
|
|
"does not exist.";
|
2016-12-21 10:13:38 +01:00
|
|
|
}
|
|
|
|
|
return QVersionNumber();
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
static QByteArray decodeProvisioningProfile(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!path.isEmpty(), return QByteArray());
|
|
|
|
|
|
|
|
|
|
Utils::SynchronousProcess p;
|
|
|
|
|
p.setTimeoutS(3);
|
|
|
|
|
// path is assumed to be valid file path to .mobileprovision
|
|
|
|
|
const QStringList args = {"smime", "-inform", "der", "-verify", "-in", path};
|
|
|
|
|
Utils::SynchronousProcessResponse res = p.runBlocking("openssl", args);
|
|
|
|
|
if (res.result != Utils::SynchronousProcessResponse::Finished)
|
|
|
|
|
qCDebug(iosCommonLog) << "Reading signed provisioning file failed" << path;
|
|
|
|
|
return res.stdOut().toLatin1();
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-09 09:50:07 +02:00
|
|
|
void IosConfigurations::updateAutomaticKitList()
|
|
|
|
|
{
|
2017-05-24 23:26:35 -07:00
|
|
|
const QList<XcodePlatform> platforms = XcodeProbe::detectPlatforms().values();
|
2015-10-09 09:50:07 +02:00
|
|
|
if (!platforms.isEmpty())
|
|
|
|
|
setDeveloperPath(platforms.first().developerPath);
|
|
|
|
|
qCDebug(kitSetupLog) << "Developer path:" << developerPath();
|
|
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
// target -> tool chain
|
|
|
|
|
const auto targetToolChainHash = findToolChains(platforms);
|
2015-10-09 09:50:07 +02:00
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
const auto qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) {
|
|
|
|
|
return v->isValid() && v->type() == Constants::IOSQT;
|
|
|
|
|
}).toSet();
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2015-02-03 23:49:46 +02:00
|
|
|
const DebuggerItem *possibleDebugger = DebuggerItemManager::findByEngineType(LldbEngineType);
|
2015-10-09 09:50:07 +02:00
|
|
|
const QVariant debuggerId = (possibleDebugger ? possibleDebugger->id() : QVariant());
|
|
|
|
|
|
|
|
|
|
QSet<Kit *> existingKits = existingAutoDetectedIosKits();
|
|
|
|
|
qCDebug(kitSetupLog) << "Existing auto-detected iOS kits:";
|
|
|
|
|
printKits(existingKits);
|
|
|
|
|
QSet<Kit *> resultingKits;
|
2017-05-24 23:26:35 -07:00
|
|
|
for (const XcodePlatform &platform : platforms) {
|
|
|
|
|
for (const auto &sdk : platform.sdks) {
|
|
|
|
|
const auto targets = Utils::filtered(platform.targets,
|
|
|
|
|
[&sdk](const XcodePlatform::ToolchainTarget &target) {
|
|
|
|
|
return sdk.architectures.first() == target.architecture;
|
2015-10-09 09:50:07 +02:00
|
|
|
});
|
2017-05-24 23:26:35 -07:00
|
|
|
if (targets.empty())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const auto target = targets.front();
|
|
|
|
|
|
|
|
|
|
const ToolChainPair &platformToolchains = targetToolChainHash.value(target);
|
|
|
|
|
if (!platformToolchains.first && !platformToolchains.second) {
|
|
|
|
|
qCDebug(kitSetupLog) << " - No tool chain found";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Core::Id pDeviceType = deviceId(sdk.directoryName);
|
2017-07-27 13:31:15 +02:00
|
|
|
if (!pDeviceType.isValid()) {
|
|
|
|
|
qCDebug(kitSetupLog) << "Unsupported/Invalid device type" << sdk.directoryName;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2017-05-24 23:26:35 -07:00
|
|
|
|
|
|
|
|
for (BaseQtVersion *qtVersion : qtVersions) {
|
|
|
|
|
qCDebug(kitSetupLog) << " - Qt version:" << qtVersion->displayName();
|
|
|
|
|
Kit *kit = Utils::findOrDefault(existingKits, [&pDeviceType, &platformToolchains, &qtVersion](const Kit *kit) {
|
|
|
|
|
// we do not compare the sdk (thus automatically upgrading it in place if a
|
|
|
|
|
// new Xcode is used). Change?
|
|
|
|
|
return DeviceTypeKitInformation::deviceTypeId(kit) == pDeviceType
|
|
|
|
|
&& ToolChainKitInformation::toolChain(kit, ProjectExplorer::Constants::CXX_LANGUAGE_ID) == platformToolchains.second
|
|
|
|
|
&& ToolChainKitInformation::toolChain(kit, ProjectExplorer::Constants::C_LANGUAGE_ID) == platformToolchains.first
|
|
|
|
|
&& QtKitInformation::qtVersion(kit) == qtVersion;
|
|
|
|
|
});
|
|
|
|
|
QTC_ASSERT(!resultingKits.contains(kit), continue);
|
|
|
|
|
if (kit) {
|
|
|
|
|
qCDebug(kitSetupLog) << " - Kit matches:" << kit->displayName();
|
|
|
|
|
kit->blockNotification();
|
|
|
|
|
setupKit(kit, pDeviceType, platformToolchains, debuggerId, sdk.path, qtVersion);
|
|
|
|
|
kit->unblockNotification();
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(kitSetupLog) << " - Setting up new kit";
|
2018-07-16 15:32:23 +02:00
|
|
|
auto newKit = std::make_unique<Kit>();
|
|
|
|
|
kit = newKit.get();
|
2017-05-24 23:26:35 -07:00
|
|
|
kit->blockNotification();
|
|
|
|
|
kit->setAutoDetected(true);
|
|
|
|
|
const QString baseDisplayName = isSimulatorDeviceId(pDeviceType)
|
|
|
|
|
? tr("%1 Simulator").arg(qtVersion->unexpandedDisplayName())
|
|
|
|
|
: qtVersion->unexpandedDisplayName();
|
|
|
|
|
kit->setUnexpandedDisplayName(baseDisplayName);
|
|
|
|
|
setupKit(kit, pDeviceType, platformToolchains, debuggerId, sdk.path, qtVersion);
|
|
|
|
|
kit->unblockNotification();
|
2018-07-16 15:32:23 +02:00
|
|
|
KitManager::registerKit(std::move(newKit));
|
2017-05-24 23:26:35 -07:00
|
|
|
}
|
|
|
|
|
resultingKits.insert(kit);
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-09 09:50:07 +02:00
|
|
|
// remove unused kits
|
|
|
|
|
existingKits.subtract(resultingKits);
|
|
|
|
|
qCDebug(kitSetupLog) << "Removing unused kits:";
|
|
|
|
|
printKits(existingKits);
|
|
|
|
|
foreach (Kit *kit, existingKits)
|
|
|
|
|
KitManager::deregisterKit(kit);
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
static IosConfigurations *m_instance = nullptr;
|
2013-10-14 17:07:51 +02:00
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
IosConfigurations *IosConfigurations::instance()
|
2013-04-25 16:02:17 +02:00
|
|
|
{
|
2013-10-07 20:14:54 +02:00
|
|
|
return m_instance;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-14 17:07:51 +02:00
|
|
|
void IosConfigurations::initialize()
|
|
|
|
|
{
|
2018-07-16 15:32:23 +02:00
|
|
|
QTC_CHECK(m_instance == nullptr);
|
|
|
|
|
m_instance = new IosConfigurations(nullptr);
|
2013-10-14 17:07:51 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-30 17:42:13 +01:00
|
|
|
void IosConfigurations::kitsRestored()
|
|
|
|
|
{
|
|
|
|
|
disconnect(KitManager::instance(), &KitManager::kitsLoaded,
|
|
|
|
|
this, &IosConfigurations::kitsRestored);
|
|
|
|
|
IosConfigurations::updateAutomaticKitList();
|
|
|
|
|
connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
|
|
|
|
|
IosConfigurations::instance(), &IosConfigurations::updateAutomaticKitList);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 20:14:54 +02:00
|
|
|
bool IosConfigurations::ignoreAllDevices()
|
|
|
|
|
{
|
2013-10-14 17:07:51 +02:00
|
|
|
return m_instance->m_ignoreAllDevices;
|
2013-10-07 20:14:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IosConfigurations::setIgnoreAllDevices(bool ignoreDevices)
|
|
|
|
|
{
|
2013-10-14 17:07:51 +02:00
|
|
|
if (ignoreDevices != m_instance->m_ignoreAllDevices) {
|
|
|
|
|
m_instance->m_ignoreAllDevices = ignoreDevices;
|
|
|
|
|
m_instance->save();
|
2013-10-07 20:14:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-27 11:00:20 +01:00
|
|
|
void IosConfigurations::setScreenshotDir(const FileName &path)
|
|
|
|
|
{
|
|
|
|
|
if (m_instance->m_screenshotDir != path) {
|
|
|
|
|
m_instance->m_screenshotDir = path;
|
|
|
|
|
m_instance->save();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileName IosConfigurations::screenshotDir()
|
|
|
|
|
{
|
|
|
|
|
return m_instance->m_screenshotDir;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 20:14:54 +02:00
|
|
|
FileName IosConfigurations::developerPath()
|
|
|
|
|
{
|
2013-10-14 17:07:51 +02:00
|
|
|
return m_instance->m_developerPath;
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
2016-12-21 10:13:38 +01:00
|
|
|
QVersionNumber IosConfigurations::xcodeVersion()
|
|
|
|
|
{
|
|
|
|
|
return m_instance->m_xcodeVersion;
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
void IosConfigurations::save()
|
|
|
|
|
{
|
2013-10-14 17:07:51 +02:00
|
|
|
QSettings *settings = Core::ICore::settings();
|
2013-04-25 16:02:17 +02:00
|
|
|
settings->beginGroup(SettingsGroup);
|
2013-10-07 20:14:54 +02:00
|
|
|
settings->setValue(ignoreAllDevicesKey, m_ignoreAllDevices);
|
2017-01-27 11:00:20 +01:00
|
|
|
settings->setValue(screenshotDirPathKey, m_screenshotDir.toString());
|
2013-04-25 16:02:17 +02:00
|
|
|
settings->endGroup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IosConfigurations::IosConfigurations(QObject *parent)
|
|
|
|
|
: QObject(parent)
|
|
|
|
|
{
|
|
|
|
|
load();
|
2019-01-30 17:42:13 +01:00
|
|
|
connect(KitManager::instance(), &KitManager::kitsLoaded,
|
|
|
|
|
this, &IosConfigurations::kitsRestored);
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IosConfigurations::load()
|
|
|
|
|
{
|
2013-10-14 17:07:51 +02:00
|
|
|
QSettings *settings = Core::ICore::settings();
|
2013-04-25 16:02:17 +02:00
|
|
|
settings->beginGroup(SettingsGroup);
|
2013-10-07 20:14:54 +02:00
|
|
|
m_ignoreAllDevices = settings->value(ignoreAllDevicesKey, false).toBool();
|
2017-01-27 11:00:20 +01:00
|
|
|
m_screenshotDir = FileName::fromString(settings->value(screenshotDirPathKey).toString());
|
|
|
|
|
if (!m_screenshotDir.exists()) {
|
2019-01-17 01:54:13 +01:00
|
|
|
QString defaultDir =
|
|
|
|
|
QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).constFirst();
|
2017-01-27 11:00:20 +01:00
|
|
|
m_screenshotDir = FileName::fromString(defaultDir);
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
settings->endGroup();
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-14 17:07:51 +02:00
|
|
|
void IosConfigurations::updateSimulators()
|
|
|
|
|
{
|
2013-04-25 16:02:17 +02:00
|
|
|
// currently we have just one simulator
|
|
|
|
|
DeviceManager *devManager = DeviceManager::instance();
|
2013-10-14 17:07:51 +02:00
|
|
|
Core::Id devId = Constants::IOS_SIMULATOR_DEVICE_ID;
|
2013-04-25 16:02:17 +02:00
|
|
|
IDevice::ConstPtr dev = devManager->find(devId);
|
2014-11-12 19:41:59 +01:00
|
|
|
if (dev.isNull()) {
|
|
|
|
|
dev = IDevice::ConstPtr(new IosSimulator(devId));
|
|
|
|
|
devManager->addDevice(dev);
|
|
|
|
|
}
|
2016-09-26 12:40:09 +02:00
|
|
|
SimulatorControl::updateAvailableSimulators();
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-07 20:14:54 +02:00
|
|
|
void IosConfigurations::setDeveloperPath(const FileName &devPath)
|
|
|
|
|
{
|
2014-11-25 15:42:26 +01:00
|
|
|
static bool hasDevPath = false;
|
2013-10-14 17:07:51 +02:00
|
|
|
if (devPath != m_instance->m_developerPath) {
|
|
|
|
|
m_instance->m_developerPath = devPath;
|
|
|
|
|
m_instance->save();
|
2014-11-25 15:42:26 +01:00
|
|
|
if (!hasDevPath && !devPath.isEmpty()) {
|
|
|
|
|
hasDevPath = true;
|
2016-06-29 19:35:23 +03:00
|
|
|
QTimer::singleShot(1000, IosDeviceManager::instance(),
|
|
|
|
|
&IosDeviceManager::monitorAvailableDevices);
|
2014-11-25 15:42:26 +01:00
|
|
|
m_instance->updateSimulators();
|
2016-12-21 10:13:38 +01:00
|
|
|
|
|
|
|
|
// Find xcode version.
|
2017-10-10 13:01:06 +02:00
|
|
|
m_instance->m_xcodeVersion = findXcodeVersion(m_instance->m_developerPath);
|
2014-11-25 15:42:26 +01:00
|
|
|
}
|
2013-10-07 20:14:54 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
void IosConfigurations::initializeProvisioningData()
|
|
|
|
|
{
|
|
|
|
|
// Initialize provisioning data only on demand. i.e. when first call to a provisioing data API
|
|
|
|
|
// is made.
|
2017-05-29 17:26:10 +02:00
|
|
|
if (m_provisioningDataWatcher)
|
2017-03-03 16:20:53 +01:00
|
|
|
return;
|
|
|
|
|
|
2017-06-06 14:58:40 +02:00
|
|
|
// Instantiate m_provisioningDataWatcher to mark initialized before calling loadProvisioningData.
|
|
|
|
|
m_provisioningDataWatcher = new QFileSystemWatcher(this);
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
m_instance->loadProvisioningData(false);
|
|
|
|
|
|
|
|
|
|
// Watch the provisioing profiles folder and xcode plist for changes and
|
|
|
|
|
// update the content accordingly.
|
|
|
|
|
m_provisioningDataWatcher->addPath(xcodePlistPath);
|
|
|
|
|
m_provisioningDataWatcher->addPath(provisioningProfileDirPath);
|
|
|
|
|
connect(m_provisioningDataWatcher, &QFileSystemWatcher::directoryChanged,
|
|
|
|
|
std::bind(&IosConfigurations::loadProvisioningData, this, true));
|
|
|
|
|
connect(m_provisioningDataWatcher, &QFileSystemWatcher::fileChanged,
|
|
|
|
|
std::bind(&IosConfigurations::loadProvisioningData, this, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IosConfigurations::loadProvisioningData(bool notify)
|
|
|
|
|
{
|
|
|
|
|
m_developerTeams.clear();
|
|
|
|
|
m_provisioningProfiles.clear();
|
|
|
|
|
|
|
|
|
|
// Populate Team id's
|
|
|
|
|
const QSettings xcodeSettings(xcodePlistPath, QSettings::NativeFormat);
|
|
|
|
|
const QVariantMap teamMap = xcodeSettings.value(provisioningTeamsTag).toMap();
|
|
|
|
|
QList<QVariantMap> teams;
|
|
|
|
|
QMapIterator<QString, QVariant> accountiterator(teamMap);
|
|
|
|
|
while (accountiterator.hasNext()) {
|
|
|
|
|
accountiterator.next();
|
|
|
|
|
QVariantMap teamInfo = accountiterator.value().toMap();
|
|
|
|
|
int provisioningTeamIsFree = teamInfo.value(freeTeamTag).toBool() ? 1 : 0;
|
|
|
|
|
teamInfo[freeTeamTag] = provisioningTeamIsFree;
|
|
|
|
|
teamInfo[emailTag] = accountiterator.key();
|
|
|
|
|
teams.append(teamInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort team id's to move the free provisioning teams at last of the list.
|
|
|
|
|
Utils::sort(teams, [](const QVariantMap &teamInfo1, const QVariantMap &teamInfo2) {
|
|
|
|
|
return teamInfo1.value(freeTeamTag).toInt() < teamInfo2.value(freeTeamTag).toInt();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
foreach (auto teamInfo, teams) {
|
2017-03-14 20:25:06 +02:00
|
|
|
auto team = std::make_shared<DevelopmentTeam>();
|
2017-03-03 16:20:53 +01:00
|
|
|
team->m_name = teamInfo.value(teamNameTag).toString();
|
|
|
|
|
team->m_email = teamInfo.value(emailTag).toString();
|
|
|
|
|
team->m_identifier = teamInfo.value(teamIdTag).toString();
|
|
|
|
|
team->m_freeTeam = teamInfo.value(freeTeamTag).toInt() == 1;
|
|
|
|
|
m_developerTeams.append(team);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QDir provisioningProflesDir(provisioningProfileDirPath);
|
2017-03-16 12:34:37 +01:00
|
|
|
const QStringList filters = {"*.mobileprovision"};
|
|
|
|
|
foreach (QFileInfo fileInfo, provisioningProflesDir.entryInfoList(filters, QDir::NoDotAndDotDot | QDir::Files)) {
|
2017-03-03 16:20:53 +01:00
|
|
|
QDomDocument provisioningDoc;
|
2017-03-14 20:25:06 +02:00
|
|
|
auto profile = std::make_shared<ProvisioningProfile>();
|
2017-03-03 16:20:53 +01:00
|
|
|
QString teamID;
|
|
|
|
|
if (provisioningDoc.setContent(decodeProvisioningProfile(fileInfo.absoluteFilePath()))) {
|
|
|
|
|
QDomNodeList nodes = provisioningDoc.elementsByTagName("key");
|
|
|
|
|
for (int i = 0;i<nodes.count(); ++i) {
|
|
|
|
|
QDomElement e = nodes.at(i).toElement();
|
|
|
|
|
|
|
|
|
|
if (e.text().compare(udidTag) == 0)
|
|
|
|
|
profile->m_identifier = e.nextSiblingElement().text();
|
|
|
|
|
|
|
|
|
|
if (e.text().compare(profileNameTag) == 0)
|
|
|
|
|
profile->m_name = e.nextSiblingElement().text();
|
|
|
|
|
|
|
|
|
|
if (e.text().compare(appIdTag) == 0)
|
|
|
|
|
profile->m_appID = e.nextSiblingElement().text();
|
|
|
|
|
|
|
|
|
|
if (e.text().compare(expirationDateTag) == 0)
|
|
|
|
|
profile->m_expirationDate = QDateTime::fromString(e.nextSiblingElement().text(),
|
|
|
|
|
Qt::ISODate).toUTC();
|
|
|
|
|
|
|
|
|
|
if (e.text().compare(profileTeamIdTag) == 0) {
|
|
|
|
|
teamID = e.nextSibling().firstChildElement().text();
|
|
|
|
|
auto team = developmentTeam(teamID);
|
|
|
|
|
if (team) {
|
|
|
|
|
profile->m_team = team;
|
|
|
|
|
team->m_profiles.append(profile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(iosCommonLog) << "Error reading provisoing profile" << fileInfo.absoluteFilePath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (profile->m_team)
|
|
|
|
|
m_provisioningProfiles.append(profile);
|
|
|
|
|
else
|
|
|
|
|
qCDebug(iosCommonLog) << "Skipping profile. No corresponding team found" << profile;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (notify)
|
|
|
|
|
emit provisioningDataChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DevelopmentTeams &IosConfigurations::developmentTeams()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_instance);
|
|
|
|
|
m_instance->initializeProvisioningData();
|
|
|
|
|
return m_instance->m_developerTeams;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DevelopmentTeamPtr IosConfigurations::developmentTeam(const QString &teamID)
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_instance);
|
|
|
|
|
m_instance->initializeProvisioningData();
|
|
|
|
|
return findOrDefault(m_instance->m_developerTeams,
|
|
|
|
|
Utils::equal(&DevelopmentTeam::identifier, teamID));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ProvisioningProfiles &IosConfigurations::provisioningProfiles()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_instance);
|
|
|
|
|
m_instance->initializeProvisioningData();
|
|
|
|
|
return m_instance->m_provisioningProfiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProvisioningProfilePtr IosConfigurations::provisioningProfile(const QString &profileID)
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_instance);
|
|
|
|
|
m_instance->initializeProvisioningData();
|
|
|
|
|
return Utils::findOrDefault(m_instance->m_provisioningProfiles,
|
|
|
|
|
Utils::equal(&ProvisioningProfile::identifier, profileID));
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
static ClangToolChain *createToolChain(const XcodePlatform &platform,
|
|
|
|
|
const XcodePlatform::ToolchainTarget &target,
|
|
|
|
|
Core::Id l)
|
2015-10-09 09:50:07 +02:00
|
|
|
{
|
2016-12-16 00:43:14 +01:00
|
|
|
if (!l.isValid())
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
if (l != Core::Id(ProjectExplorer::Constants::C_LANGUAGE_ID)
|
|
|
|
|
&& l != Core::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID))
|
2016-11-16 14:59:10 +01:00
|
|
|
return nullptr;
|
|
|
|
|
|
2018-11-12 19:55:59 +01:00
|
|
|
auto toolChain = new ClangToolChain(ToolChain::AutoDetection);
|
2016-11-16 14:59:10 +01:00
|
|
|
toolChain->setLanguage(l);
|
2017-05-24 23:26:35 -07:00
|
|
|
toolChain->setDisplayName(target.name);
|
|
|
|
|
toolChain->setPlatformCodeGenFlags(target.backendFlags);
|
|
|
|
|
toolChain->setPlatformLinkerFlags(target.backendFlags);
|
2016-12-16 00:43:14 +01:00
|
|
|
toolChain->resetToolChain(l == Core::Id(ProjectExplorer::Constants::CXX_LANGUAGE_ID) ?
|
2016-11-16 14:59:10 +01:00
|
|
|
platform.cxxCompilerPath : platform.cCompilerPath);
|
2017-05-24 23:26:35 -07:00
|
|
|
|
2015-10-09 09:50:07 +02:00
|
|
|
return toolChain;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-16 00:43:14 +01:00
|
|
|
QSet<Core::Id> IosToolChainFactory::supportedLanguages() const
|
2016-07-12 11:33:17 +02:00
|
|
|
{
|
2017-05-24 23:26:35 -07:00
|
|
|
return {
|
|
|
|
|
ProjectExplorer::Constants::C_LANGUAGE_ID,
|
|
|
|
|
ProjectExplorer::Constants::CXX_LANGUAGE_ID
|
|
|
|
|
};
|
2016-07-12 11:33:17 +02:00
|
|
|
}
|
|
|
|
|
|
2015-10-09 09:50:07 +02:00
|
|
|
QList<ToolChain *> IosToolChainFactory::autoDetect(const QList<ToolChain *> &existingToolChains)
|
|
|
|
|
{
|
|
|
|
|
QList<ClangToolChain *> existingClangToolChains = clangToolChains(existingToolChains);
|
2017-05-24 23:26:35 -07:00
|
|
|
const QList<XcodePlatform> platforms = XcodeProbe::detectPlatforms().values();
|
2018-05-29 10:59:27 +02:00
|
|
|
QList<ToolChain *> toolChains;
|
2015-10-09 09:50:07 +02:00
|
|
|
toolChains.reserve(platforms.size());
|
2017-05-24 23:26:35 -07:00
|
|
|
foreach (const XcodePlatform &platform, platforms) {
|
|
|
|
|
for (const XcodePlatform::ToolchainTarget &target : platform.targets) {
|
|
|
|
|
ToolChainPair platformToolchains = findToolChainForPlatform(platform, target,
|
|
|
|
|
existingClangToolChains);
|
|
|
|
|
auto createOrAdd = [&](ClangToolChain* toolChain, Core::Id l) {
|
|
|
|
|
if (!toolChain) {
|
|
|
|
|
toolChain = createToolChain(platform, target, l);
|
|
|
|
|
existingClangToolChains.append(toolChain);
|
|
|
|
|
}
|
|
|
|
|
toolChains.append(toolChain);
|
|
|
|
|
};
|
2016-11-16 14:59:10 +01:00
|
|
|
|
2017-05-24 23:26:35 -07:00
|
|
|
createOrAdd(platformToolchains.first, ProjectExplorer::Constants::C_LANGUAGE_ID);
|
|
|
|
|
createOrAdd(platformToolchains.second, ProjectExplorer::Constants::CXX_LANGUAGE_ID);
|
|
|
|
|
}
|
2015-10-09 09:50:07 +02:00
|
|
|
}
|
2018-05-29 10:59:27 +02:00
|
|
|
return toolChains;
|
2015-10-09 09:50:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-03-03 16:20:53 +01:00
|
|
|
QString DevelopmentTeam::identifier() const
|
|
|
|
|
{
|
|
|
|
|
return m_identifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DevelopmentTeam::displayName() const
|
|
|
|
|
{
|
|
|
|
|
return QString("%1 - %2").arg(m_email).arg(m_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DevelopmentTeam::details() const
|
|
|
|
|
{
|
|
|
|
|
return tr("%1 - Free Provisioning Team : %2")
|
|
|
|
|
.arg(m_identifier).arg(m_freeTeam ? tr("Yes") : tr("No"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDebug &operator<<(QDebug &stream, DevelopmentTeamPtr team)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(team, return stream);
|
|
|
|
|
stream << team->displayName() << team->identifier() << team->isFreeProfile();
|
|
|
|
|
foreach (auto profile, team->m_profiles)
|
|
|
|
|
stream << "Profile:" << profile;
|
|
|
|
|
return stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProvisioningProfile::identifier() const
|
|
|
|
|
{
|
|
|
|
|
return m_identifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProvisioningProfile::displayName() const
|
|
|
|
|
{
|
|
|
|
|
return m_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ProvisioningProfile::details() const
|
|
|
|
|
{
|
|
|
|
|
return tr("Team: %1\nApp ID: %2\nExpiration date: %3").arg(m_team->identifier()).arg(m_appID)
|
|
|
|
|
.arg(m_expirationDate.toLocalTime().toString(Qt::SystemLocaleShortDate));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDebug &operator<<(QDebug &stream, std::shared_ptr<ProvisioningProfile> profile)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(profile, return stream);
|
|
|
|
|
return stream << profile->displayName() << profile->identifier() << profile->details();
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Ios
|