iOS: Avoid iostool for info gathering if possible

Previously we had a small workaround for retrieving the state of
developer mode for iOS 17 devices integrated into our own iostool.

Instead use devicectl for gathering device information for devices that
it can handle, and only fall back to iostool for the devices that it
cannot handle.

Since iostool cannot handle deployment, running, and debugging for
iOS 17 devices anyway, the end goal would be to only use devicectl
for these.

Also add a TaskTree wrapper for IosToolHandler for convenience.

Change-Id: I5bcd09eb354c2dce9b21e62e140de16f2e740d6e
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Eike Ziller
2024-01-10 10:52:04 +01:00
parent e595c3f615
commit 150441bf88
5 changed files with 127 additions and 44 deletions

View File

@@ -16,8 +16,13 @@
#include <projectexplorer/kitaspects.h>
#include <utils/portlist.h>
#include <utils/process.h>
#include <solutions/tasking/tasktree.h>
#include <QFormLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QLabel>
#include <QMessageBox>
@@ -72,7 +77,14 @@ static QString CFStringRef2QString(CFStringRef s)
namespace Ios::Internal {
const char kDeviceName[] = "deviceName";
const char kDeveloperStatus[] = "developerStatus";
const char kDeviceConnected[] = "deviceConnected";
const char kOsVersion[] = "osVersion";
const char kCpuArchitecture[] = "cpuArchitecture";
const char kUniqueDeviceId[] = "uniqueDeviceId";
const char vOff[] = "*off*";
const char vDevelopment[] = "Development";
const char vYes[] = "YES";
class IosDeviceInfoWidget : public IDeviceWidget
{
@@ -168,12 +180,12 @@ QString IosDevice::name()
QString IosDevice::osVersion() const
{
return m_extraInfo.value(QLatin1String("osVersion"));
return m_extraInfo.value(kOsVersion);
}
QString IosDevice::cpuArchitecture() const
{
return m_extraInfo.value("cpuArchitecture");
return m_extraInfo.value(kCpuArchitecture);
}
Utils::Port IosDevice::nextPort() const
@@ -194,13 +206,12 @@ IosDeviceManager::TranslationMap IosDeviceManager::translationMap()
TranslationMap &tMap = *new TranslationMap;
tMap[kDeviceName] = Tr::tr("Device name");
//: Whether the device is in developer mode.
tMap[QLatin1String("developerStatus")] = Tr::tr("Developer status");
tMap[QLatin1String("deviceConnected")] = Tr::tr("Connected");
tMap[QLatin1String("YES")] = Tr::tr("yes");
tMap[kDeveloperStatus] = Tr::tr("Developer status");
tMap[kDeviceConnected] = Tr::tr("Connected");
tMap[vYes] = Tr::tr("yes");
tMap[QLatin1String("NO")] = Tr::tr("no");
tMap[QLatin1String("YES")] = Tr::tr("yes");
tMap[QLatin1String("*unknown*")] = Tr::tr("unknown");
tMap[QLatin1String("osVersion")] = Tr::tr("OS version");
tMap[kOsVersion] = Tr::tr("OS version");
translationMap = &tMap;
return tMap;
}
@@ -253,12 +264,61 @@ void IosDeviceManager::deviceDisconnected(const QString &uid)
void IosDeviceManager::updateInfo(const QString &devId)
{
IosToolHandler *requester = new IosToolHandler(IosDeviceType(IosDeviceType::IosDevice), this);
connect(requester, &IosToolHandler::deviceInfo,
this, &IosDeviceManager::deviceInfo, Qt::QueuedConnection);
connect(requester, &IosToolHandler::finished,
this, &IosDeviceManager::infoGathererFinished);
requester->requestDeviceInfo(devId);
using namespace Tasking;
const auto infoFromDeviceCtl = ProcessTask(
[](Process &process) {
process.setCommand({FilePath::fromString("/usr/bin/xcrun"),
{"devicectl", "list", "devices", "--quiet", "--json-output", "-"}});
},
[this, devId](const Process &process, DoneWith) -> DoneResult {
auto jsonOutput = QJsonDocument::fromJson(process.rawStdOut());
// find device
const QJsonArray deviceList = jsonOutput["result"]["devices"].toArray();
for (const QJsonValue &device : deviceList) {
const QString udid = device["hardwareProperties"]["udid"].toString();
// USB identifiers don't have dashes, but iOS device udids can. Remove.
if (QString(udid).remove('-') == devId) {
// fill in the map that we use for the iostool data
QMap<QString, QString> info;
info[kDeviceName] = device["deviceProperties"]["name"].toString();
info[kDeveloperStatus] = QLatin1String(
device["deviceProperties"]["developerModeStatus"] == "enabled"
? vDevelopment
: vOff);
info[kDeviceConnected] = vYes; // that's the assumption
info[kOsVersion]
= QLatin1String("%1 (%2)")
.arg(device["deviceProperties"]["osVersionNumber"].toString(),
device["deviceProperties"]["osBuildUpdate"].toString());
info[kCpuArchitecture]
= device["hardwareProperties"]["cpuType"]["name"].toString();
info[kUniqueDeviceId] = udid;
deviceInfo(nullptr, devId, info);
return DoneResult::Success;
}
}
// device not found, not handled by devicectl
return DoneResult::Error;
},
CallDoneIf::Success);
const auto infoFromIosTool = IosToolTask([this, devId](IosToolRunner &runner) {
runner.setDeviceType(IosDeviceType::IosDevice);
runner.setStartHandler([this, devId](IosToolHandler *handler) {
connect(handler,
&IosToolHandler::deviceInfo,
this,
&IosDeviceManager::deviceInfo,
Qt::QueuedConnection);
handler->requestDeviceInfo(devId);
});
});
const Group root{sequential, stopOnSuccess, infoFromDeviceCtl, infoFromIosTool};
auto task = new TaskTree(root);
connect(task, &TaskTree::done, task, [task] { task->deleteLater(); });
task->start();
}
void IosDeviceManager::deviceInfo(IosToolHandler *, const QString &uid,
@@ -294,14 +354,14 @@ void IosDeviceManager::deviceInfo(IosToolHandler *, const QString &uid,
QLatin1String devStatusKey = QLatin1String("developerStatus");
if (info.contains(devStatusKey)) {
QString devStatus = info.value(devStatusKey);
if (devStatus == QLatin1String("Development")) {
if (devStatus == vDevelopment) {
devManager->setDeviceState(newDev->id(), IDevice::DeviceReadyToUse);
m_userModeDeviceIds.removeOne(uid);
} else {
devManager->setDeviceState(newDev->id(), IDevice::DeviceConnected);
bool shouldIgnore = newDev->m_ignoreDevice;
newDev->m_ignoreDevice = true;
if (devStatus == QLatin1String("*off*")) {
if (devStatus == vOff) {
if (!shouldIgnore && !IosConfigurations::ignoreAllDevices()) {
QMessageBox mBox;
mBox.setText(Tr::tr("An iOS device in user mode has been detected."));
@@ -331,11 +391,6 @@ void IosDeviceManager::deviceInfo(IosToolHandler *, const QString &uid,
}
}
void IosDeviceManager::infoGathererFinished(IosToolHandler *gatherer)
{
gatherer->deleteLater();
}
#ifdef Q_OS_MAC
namespace {
io_iterator_t gAddedIter;

View File

@@ -75,7 +75,6 @@ public:
void updateInfo(const QString &devId);
void deviceInfo(Ios::IosToolHandler *gatherer, const QString &deviceId,
const Ios::IosToolHandler::Dict &info);
void infoGathererFinished(Ios::IosToolHandler *gatherer);
void monitorAvailableDevices();
private:

View File

@@ -973,6 +973,28 @@ bool IosToolHandler::isRunning() const
return d->isRunning();
}
void IosToolRunner::setStartHandler(const StartHandler &startHandler)
{
m_startHandler = startHandler;
}
void IosToolRunner::setDeviceType(const Internal::IosDeviceType &type)
{
m_deviceType = type;
}
IosToolTaskAdapter::IosToolTaskAdapter() {}
void IosToolTaskAdapter::start()
{
task()->m_iosToolHandler = new IosToolHandler(Internal::IosDeviceType(task()->m_deviceType));
connect(task()->m_iosToolHandler, &IosToolHandler::finished, this, [this] {
task()->m_iosToolHandler->deleteLater();
emit done(Tasking::DoneResult::Success);
});
task()->m_startHandler(task()->m_iosToolHandler);
}
} // namespace Ios
#include "iostoolhandler.moc"

View File

@@ -3,9 +3,13 @@
#pragma once
#include "iossimulator.h"
#include <utils/filepath.h>
#include <utils/port.h>
#include <solutions/tasking/tasktree.h>
#include <QObject>
#include <QMap>
#include <QString>
@@ -67,4 +71,29 @@ private:
Ios::Internal::IosToolHandlerPrivate *d;
};
// for Tasking:
class IosToolRunner
{
public:
using StartHandler = std::function<void(IosToolHandler *)>;
void setStartHandler(const StartHandler &startHandler);
void setDeviceType(const Internal::IosDeviceType &type);
private:
friend class IosToolTaskAdapter;
IosToolHandler *m_iosToolHandler = nullptr;
StartHandler m_startHandler;
Internal::IosDeviceType m_deviceType = Internal::IosDeviceType::IosDevice;
};
class IosToolTaskAdapter : public Tasking::TaskAdapter<IosToolRunner>
{
public:
IosToolTaskAdapter();
void start() final;
};
using IosToolTask = Tasking::CustomTask<IosToolTaskAdapter>;
} // namespace Ios

View File

@@ -21,7 +21,6 @@
#include <QTimer>
#include <QUrl>
#include <QVersionNumber>
#include <QJsonDocument>
#include <CoreFoundation/CoreFoundation.h>
@@ -105,20 +104,6 @@ static bool findXcodePath(QString *xcodePath)
return (process.exitStatus() == QProcess::NormalExit && QFile::exists(*xcodePath));
}
static bool checkDevelopmentStatusViaDeviceCtl(const QString &deviceId)
{
QProcess process;
process.start("/usr/bin/xcrun", QStringList({"devicectl",
"device", "info", "details", "--quiet", "--device", deviceId, "-j", "-"}));
if (!process.waitForFinished(3000)) {
qCWarning(loggingCategory) << "Failed to launch devicectl:" << process.errorString();
return false;
}
auto jsonOutput = QJsonDocument::fromJson(process.readAllStandardOutput());
return jsonOutput["result"]["deviceProperties"]["developerModeStatus"] == "enabled";
}
/*!
* \brief Finds the \e DeveloperDiskImage.dmg path corresponding to \a versionStr and \a buildStr.
*
@@ -1685,17 +1670,10 @@ void DevInfoSession::deviceCallbackReturned()
res[deviceNameKey] = getStringValue(device, nullptr, CFSTR("DeviceName"));
res[uniqueDeviceId] = getStringValue(device, nullptr, CFSTR("UniqueDeviceID"));
const QString productVersion = getStringValue(device, nullptr, CFSTR("ProductVersion"));
if (productVersion.startsWith("17.")) {
res[developerStatusKey] = checkDevelopmentStatusViaDeviceCtl(res[uniqueDeviceId])
? QLatin1String("Development") : QLatin1String("*off*");
} else {
res[developerStatusKey] = getStringValue(device,
CFSTR("com.apple.xcode.developerdomain"),
CFSTR("DeveloperStatus"),
"*off*");
}
res[cpuArchitectureKey] = getStringValue(device, nullptr, CFSTR("CPUArchitecture"));
const QString buildVersion = getStringValue(device, nullptr, CFSTR("BuildVersion"));
if (!productVersion.isEmpty() && !buildVersion.isEmpty())