iOS: Extend SimulatorControl

Add new API's to extend iOS simulator support in Qt Creator

Task-number: QTCREATORBUG-17602
Change-Id: I7e949762a7740f2ffb927f25f327b3e82cd8e180
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Vikas Pachdha
2017-01-23 15:39:01 +01:00
parent c286639478
commit 00d424eadb
2 changed files with 236 additions and 17 deletions

View File

@@ -54,7 +54,19 @@ namespace Ios {
namespace Internal {
static int SIMULATOR_START_TIMEOUT = 60000;
static QString SIM_UDID_TAG = QStringLiteral("SimUdid");
// simctl Json Tags and tokens.
static QString DeviceTypeTag = QStringLiteral("devicetypes");
static QString DevicesTag = QStringLiteral("devices");
static QString AvailabilityTag = QStringLiteral("availability");
static QString UnavailabilityToken = QStringLiteral("unavailable");
static QString IdentifierTag = QStringLiteral("identifier");
static QString RuntimesTag = QStringLiteral("runtimes");
static QString NameTag = QStringLiteral("name");
static QString StateTag = QStringLiteral("state");
static QString UdidTag = QStringLiteral("udid");
static QString RuntimeVersionTag = QStringLiteral("version");
static QString BuildVersionTag = QStringLiteral("buildversion");
static bool checkForTimeout(const chrono::high_resolution_clock::time_point &start, int msecs = 10000)
{
@@ -81,6 +93,56 @@ static bool runSimCtlCommand(QStringList args, QByteArray *output)
return runCommand(QStringLiteral("xcrun"), args, output);
}
static QList<DeviceTypeInfo> getAvailableDeviceTypes()
{
QList<DeviceTypeInfo> deviceTypes;
QByteArray output;
runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), DeviceTypeTag}, &output);
QJsonDocument doc = QJsonDocument::fromJson(output);
if (!doc.isNull()) {
const QJsonArray runtimesArray = doc.object().value(DeviceTypeTag).toArray();
foreach (const QJsonValue deviceTypeValue, runtimesArray) {
QJsonObject deviceTypeObject = deviceTypeValue.toObject();
if (!deviceTypeObject.value(AvailabilityTag).toString().contains(UnavailabilityToken)) {
DeviceTypeInfo deviceType;
deviceType.name = deviceTypeObject.value(NameTag).toString("unknown");
deviceType.identifier = deviceTypeObject.value(IdentifierTag).toString("unknown");
deviceTypes.append(deviceType);
}
}
stable_sort(deviceTypes.begin(), deviceTypes.end());
} else {
qCDebug(simulatorLog) << "Error parsing json output from simctl. Output:" << output;
}
return deviceTypes;
}
static QList<RuntimeInfo> getAvailableRuntimes()
{
QList<RuntimeInfo> runtimes;
QByteArray output;
runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), RuntimesTag}, &output);
QJsonDocument doc = QJsonDocument::fromJson(output);
if (!doc.isNull()) {
const QJsonArray runtimesArray = doc.object().value(RuntimesTag).toArray();
foreach (const QJsonValue runtimeValue, runtimesArray) {
QJsonObject runtimeObject = runtimeValue.toObject();
if (!runtimeObject.value(AvailabilityTag).toString().contains(UnavailabilityToken)) {
RuntimeInfo runtime;
runtime.name = runtimeObject.value(NameTag).toString("unknown");
runtime.build = runtimeObject.value(BuildVersionTag).toString("unknown");
runtime.identifier = runtimeObject.value(IdentifierTag).toString("unknown");
runtime.version = runtimeObject.value(RuntimeVersionTag).toString("unknown");
runtimes.append(runtime);
}
}
stable_sort(runtimes.begin(), runtimes.end());
} else {
qCDebug(simulatorLog) << "Error parsing json output from simctl. Output:" << output;
}
return runtimes;
}
class SimulatorControlPrivate {
private:
SimulatorControlPrivate();
@@ -97,8 +159,21 @@ private:
const QString &bundleIdentifier, bool waitForDebugger,
const QStringList &extraArgs, const QString &stdoutPath,
const QString &stderrPath);
void deleteSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid);
void resetSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid);
void renameSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid, const QString &newName);
void createSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &name, const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime);
void takeSceenshot(QFutureInterface<SimulatorControl::ResponseData> &fi, const QString &simUdid,
const QString &filePath);
static QList<SimulatorInfo> availableDevices;
static QList<DeviceTypeInfo> availableDeviceTypes;
static QList<RuntimeInfo> availableRuntimes;
friend class SimulatorControl;
};
@@ -122,21 +197,21 @@ static QList<SimulatorInfo> getAllSimulatorDevices()
{
QList<SimulatorInfo> simulatorDevices;
QByteArray output;
runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), QLatin1String("devices")}, &output);
runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), DevicesTag}, &output);
QJsonDocument doc = QJsonDocument::fromJson(output);
if (!doc.isNull()) {
const QJsonObject runtimeObject = doc.object().value(QStringLiteral("devices")).toObject();
const QJsonObject runtimeObject = doc.object().value(DevicesTag).toObject();
foreach (const QString &runtime, runtimeObject.keys()) {
const QJsonArray devices = runtimeObject.value(runtime).toArray();
foreach (const QJsonValue deviceValue, devices) {
QJsonObject deviceObject = deviceValue.toObject();
SimulatorInfo device;
device.identifier = deviceObject.value(QStringLiteral("udid")).toString();
device.name = deviceObject.value(QStringLiteral("name")).toString();
device.identifier = deviceObject.value(UdidTag).toString();
device.name = deviceObject.value(NameTag).toString();
device.runtimeName = runtime;
const QString availableStr = deviceObject.value(QStringLiteral("availability")).toString();
device.available = !availableStr.contains("unavailable");
device.state = deviceObject.value(QStringLiteral("state")).toString();
const QString availableStr = deviceObject.value(AvailabilityTag).toString();
device.available = !availableStr.contains(UnavailabilityToken);
device.state = deviceObject.value(StateTag).toString();
simulatorDevices.append(device);
}
}
@@ -154,12 +229,36 @@ static QList<SimulatorInfo> getAvailableSimulators()
return availableDevices;
}
void SimulatorControl::updateAvailableSimulators()
QFuture<QList<DeviceTypeInfo> > SimulatorControl::updateDeviceTypes()
{
QFuture< QList<DeviceTypeInfo> > future = Utils::runAsync(getAvailableDeviceTypes);
Utils::onResultReady(future, [](const QList<DeviceTypeInfo> &deviceTypes) {
SimulatorControlPrivate::availableDeviceTypes = deviceTypes;
});
return future;
}
QList<RuntimeInfo> SimulatorControl::availableRuntimes()
{
return SimulatorControlPrivate::availableRuntimes;
}
QFuture<QList<RuntimeInfo> > SimulatorControl::updateRuntimes()
{
QFuture< QList<RuntimeInfo> > future = Utils::runAsync(getAvailableRuntimes);
Utils::onResultReady(future, [](const QList<RuntimeInfo> &runtimes) {
SimulatorControlPrivate::availableRuntimes = runtimes;
});
return future;
}
QFuture< QList<SimulatorInfo> > SimulatorControl::updateAvailableSimulators()
{
QFuture< QList<SimulatorInfo> > future = Utils::runAsync(getAvailableSimulators);
Utils::onResultReady(future, [](const QList<SimulatorInfo> &devices) {
SimulatorControlPrivate::availableDevices = devices;
});
return future;
}
bool SimulatorControl::isSimulatorRunning(const QString &simUdid)
@@ -199,7 +298,40 @@ SimulatorControl::launchApp(const QString &simUdid, const QString &bundleIdentif
waitForDebugger, extraArgs, stdoutPath, stderrPath);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::deleteSimulator(const QString &simUdid) const
{
return Utils::runAsync(&SimulatorControlPrivate::deleteSimulator, d, simUdid);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::resetSimulator(const QString &simUdid) const
{
return Utils::runAsync(&SimulatorControlPrivate::resetSimulator, d, simUdid);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::renameSimulator(const QString &simUdid,
const QString &newName) const
{
return Utils::runAsync(&SimulatorControlPrivate::renameSimulator, d, simUdid, newName);
}
QFuture<SimulatorControl::ResponseData>
SimulatorControl::createSimulator(const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime)
{
return Utils::runAsync(&SimulatorControlPrivate::createSimulator, d, name, deviceType, runtime);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::takeSceenshot(const QString &simUdid,
const QString &filePath)
{
return Utils::runAsync(&SimulatorControlPrivate::takeSceenshot, d, simUdid, filePath);
}
// Static members
QList<SimulatorInfo> SimulatorControlPrivate::availableDevices;
QList<DeviceTypeInfo> SimulatorControlPrivate::availableDeviceTypes;
QList<RuntimeInfo> SimulatorControlPrivate::availableRuntimes;
SimulatorControlPrivate::SimulatorControlPrivate()
{
@@ -348,5 +480,66 @@ void SimulatorControlPrivate::launchApp(QFutureInterface<SimulatorControl::Respo
}
}
void SimulatorControlPrivate::deleteSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({QStringLiteral("delete"), simUdid}, &response.commandOutput);
if (!fi.isCanceled())
fi.reportResult(response);
}
void SimulatorControlPrivate::resetSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({QStringLiteral("erase"), simUdid}, &response.commandOutput);
if (!fi.isCanceled())
fi.reportResult(response);
}
void SimulatorControlPrivate::renameSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid, const QString &newName)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({QStringLiteral("rename"), simUdid, newName},
&response.commandOutput);
if (!fi.isCanceled())
fi.reportResult(response);
}
void SimulatorControlPrivate::createSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime)
{
SimulatorControl::ResponseData response("Invalid");
if (!name.isEmpty()) {
response.success = runSimCtlCommand({ QStringLiteral("create"), name,
deviceType.identifier,
runtime.identifier },
&response.commandOutput);
response.simUdid = response.success ? QString::fromLatin1(response.commandOutput.trimmed())
: QString();
}
if (!fi.isCanceled())
fi.reportResult(response);
}
void SimulatorControlPrivate::takeSceenshot(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &simUdid, const QString &filePath)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({ QStringLiteral("io"), simUdid, QStringLiteral("screenshot"),
filePath },
&response.commandOutput);
if (!fi.isCanceled())
fi.reportResult(response);
}
} // namespace Internal
} // namespace Ios

View File

@@ -39,20 +39,33 @@ namespace Internal {
class SimulatorControlPrivate;
class SimulatorInfo {
class SimulatorEntity {
public:
QString name;
QString identifier;
bool operator <(const SimulatorEntity &o) const
{
return name < o.name;
}
};
class SimulatorInfo : public SimulatorEntity {
public:
bool isBooted() const { return state.compare(QStringLiteral("Booted")) == 0; }
bool available;
QString state;
QString runtimeName;
QString name;
QString identifier;
bool operator <(const SimulatorInfo &o) const
{
return name < o.name;
}
};
class RuntimeInfo : public SimulatorEntity{
public:
QString version;
QString build;
};
class DeviceTypeInfo : public SimulatorEntity {};
class SimulatorControl : public QObject
{
Q_OBJECT
@@ -72,8 +85,12 @@ public:
~SimulatorControl();
public:
static QList<DeviceTypeInfo> availableDeviceTypes();
static QFuture<QList<DeviceTypeInfo> > updateDeviceTypes();
static QList<RuntimeInfo> availableRuntimes();
static QFuture<QList<RuntimeInfo> > updateRuntimes();
static QList<SimulatorInfo> availableSimulators();
static void updateAvailableSimulators();
static QFuture<QList<SimulatorInfo> > updateAvailableSimulators();
static bool isSimulatorRunning(const QString &simUdid);
static QString bundleIdentifier(const Utils::FileName &bundlePath);
static QString bundleExecutable(const Utils::FileName &bundlePath);
@@ -85,6 +102,13 @@ public:
bool waitForDebugger, const QStringList &extraArgs,
const QString& stdoutPath = QString(),
const QString& stderrPath = QString()) const;
QFuture<ResponseData> deleteSimulator(const QString &simUdid) const;
QFuture<ResponseData> resetSimulator(const QString &simUdid) const;
QFuture<ResponseData> renameSimulator(const QString &simUdid, const QString &newName) const;
QFuture<ResponseData> createSimulator(const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime);
QFuture<ResponseData> takeSceenshot(const QString &simUdid, const QString &filePath);
private:
SimulatorControlPrivate *d;
@@ -92,4 +116,6 @@ private:
} // namespace Internal
} // namespace Ios
Q_DECLARE_METATYPE(Ios::Internal::DeviceTypeInfo)
Q_DECLARE_METATYPE(Ios::Internal::RuntimeInfo)
Q_DECLARE_METATYPE(Ios::Internal::SimulatorInfo)