From 150441bf88177f6c2325d832b44c8ec82ab73352 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 10 Jan 2024 10:52:04 +0100 Subject: [PATCH] iOS: Avoid iostool for info gathering if possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ø Reviewed-by: --- src/plugins/ios/iosdevice.cpp | 95 ++++++++++++++++++++------ src/plugins/ios/iosdevice.h | 1 - src/plugins/ios/iostoolhandler.cpp | 22 ++++++ src/plugins/ios/iostoolhandler.h | 29 ++++++++ src/tools/iostool/iosdevicemanager.cpp | 24 +------ 5 files changed, 127 insertions(+), 44 deletions(-) diff --git a/src/plugins/ios/iosdevice.cpp b/src/plugins/ios/iosdevice.cpp index f76f41db976..838e04de954 100644 --- a/src/plugins/ios/iosdevice.cpp +++ b/src/plugins/ios/iosdevice.cpp @@ -16,8 +16,13 @@ #include #include +#include + +#include #include +#include +#include #include #include @@ -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 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; diff --git a/src/plugins/ios/iosdevice.h b/src/plugins/ios/iosdevice.h index 24bee023de7..bdbd1d43a2c 100644 --- a/src/plugins/ios/iosdevice.h +++ b/src/plugins/ios/iosdevice.h @@ -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: diff --git a/src/plugins/ios/iostoolhandler.cpp b/src/plugins/ios/iostoolhandler.cpp index 88a15290f20..c182d780ea2 100644 --- a/src/plugins/ios/iostoolhandler.cpp +++ b/src/plugins/ios/iostoolhandler.cpp @@ -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" diff --git a/src/plugins/ios/iostoolhandler.h b/src/plugins/ios/iostoolhandler.h index a85e3250b40..33d413e3cfc 100644 --- a/src/plugins/ios/iostoolhandler.h +++ b/src/plugins/ios/iostoolhandler.h @@ -3,9 +3,13 @@ #pragma once +#include "iossimulator.h" + #include #include +#include + #include #include #include @@ -67,4 +71,29 @@ private: Ios::Internal::IosToolHandlerPrivate *d; }; +// for Tasking: + +class IosToolRunner +{ +public: + using StartHandler = std::function; + 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 +{ +public: + IosToolTaskAdapter(); + void start() final; +}; + +using IosToolTask = Tasking::CustomTask; + } // namespace Ios diff --git a/src/tools/iostool/iosdevicemanager.cpp b/src/tools/iostool/iosdevicemanager.cpp index 891e2300ec8..d492e6f78dc 100644 --- a/src/tools/iostool/iosdevicemanager.cpp +++ b/src/tools/iostool/iosdevicemanager.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include @@ -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, + 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())