diff --git a/src/plugins/ios/devicectlutils.cpp b/src/plugins/ios/devicectlutils.cpp index 4500ef6c5d0..fa012dbc6c6 100644 --- a/src/plugins/ios/devicectlutils.cpp +++ b/src/plugins/ios/devicectlutils.cpp @@ -5,9 +5,14 @@ #include "iostr.h" +#include #include -Utils::expected_str Ios::Internal::parseDevicectlResult(const QByteArray &rawOutput) +using namespace Utils; + +namespace Ios::Internal { + +expected_str parseDevicectlResult(const QByteArray &rawOutput) { // there can be crap (progress info) at front and/or end const int firstCurly = rawOutput.indexOf('{'); @@ -18,7 +23,7 @@ Utils::expected_str Ios::Internal::parseDevicectlResult(const QByteA auto jsonOutput = QJsonDocument::fromJson(rawOutput.sliced(start, end - start + 1), &parseError); if (jsonOutput.isNull()) { // parse error - return Utils::make_unexpected( + return make_unexpected( Tr::tr("Failed to parse devicectl output: %1.").arg(parseError.errorString())); } const QJsonValue errorValue = jsonOutput["error"]; @@ -37,12 +42,45 @@ Utils::expected_str Ios::Internal::parseDevicectlResult(const QByteA if (!v.isUndefined()) error += "\n" + v.toString(); } - return Utils::make_unexpected(error); + return make_unexpected(error); } const QJsonValue resultValue = jsonOutput["result"]; if (resultValue.isUndefined()) { - return Utils::make_unexpected( - Tr::tr("Failed to parse devicectl output: 'result' is missing")); + return make_unexpected(Tr::tr("Failed to parse devicectl output: 'result' is missing")); } return resultValue; } + +expected_str> parseDeviceInfo(const QByteArray &rawOutput, + const QString &deviceUsbId) +{ + const expected_str result = parseDevicectlResult(rawOutput); + if (!result) + return make_unexpected(result.error()); + // find device + const QJsonArray deviceList = (*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('-') == deviceUsbId) { + // 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; + return info; + } + } + // device not found, not handled by devicectl + // not translated, only internal logging + return make_unexpected(QLatin1String("Device is not handled by devicectl")); +} + +} // namespace Ios::Internal diff --git a/src/plugins/ios/devicectlutils.h b/src/plugins/ios/devicectlutils.h index b3f879aea72..ac239439452 100644 --- a/src/plugins/ios/devicectlutils.h +++ b/src/plugins/ios/devicectlutils.h @@ -9,6 +9,18 @@ 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"; + Utils::expected_str parseDevicectlResult(const QByteArray &rawOutput); +Utils::expected_str> parseDeviceInfo(const QByteArray &rawOutput, + const QString &deviceUsbId); } // namespace Ios::Internal diff --git a/src/plugins/ios/iosdevice.cpp b/src/plugins/ios/iosdevice.cpp index 1c17322d4fc..783d2e51991 100644 --- a/src/plugins/ios/iosdevice.cpp +++ b/src/plugins/ios/iosdevice.cpp @@ -3,6 +3,7 @@ #include "iosdevice.h" +#include "devicectlutils.h" #include "iosconfigurations.h" #include "iosconstants.h" #include "iossimulator.h" @@ -75,16 +76,6 @@ 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"; - const char kHandler[] = "Handler"; class IosDeviceInfoWidget : public IDeviceWidget @@ -283,34 +274,14 @@ void IosDeviceManager::updateInfo(const QString &devId) {"devicectl", "list", "devices", "--quiet", "--json-output", "-"}}); }, [this, devId](const Process &process) { - 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(devId, IosDevice::Handler::DeviceCtl, info); - return DoneResult::Success; - } + const expected_str> result = parseDeviceInfo(process.rawStdOut(), + devId); + if (!result) { + qCDebug(detectLog) << result.error(); + return DoneResult::Error; } - // device not found, not handled by devicectl - return DoneResult::Error; + deviceInfo(devId, IosDevice::Handler::DeviceCtl, *result); + return DoneResult::Success; }, CallDoneIf::Success); diff --git a/tests/auto/ios/devicectlutils/tst_devicectlutils.cpp b/tests/auto/ios/devicectlutils/tst_devicectlutils.cpp index 244ae7d22e4..861b2203954 100644 --- a/tests/auto/ios/devicectlutils/tst_devicectlutils.cpp +++ b/tests/auto/ios/devicectlutils/tst_devicectlutils.cpp @@ -14,6 +14,9 @@ class tst_Devicectlutils : public QObject private slots: void parseError_data(); void parseError(); + + void parseDeviceInfo_data(); + void parseDeviceInfo(); }; void tst_Devicectlutils::parseError_data() @@ -152,6 +155,156 @@ void tst_Devicectlutils::parseError() } } +void tst_Devicectlutils::parseDeviceInfo_data() +{ + QTest::addColumn("data"); + QTest::addColumn("usbId"); + QTest::addColumn("error"); + QTest::addColumn>("info"); + + const QByteArray data(R"raw( +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--quiet", + "--json-output", + "-" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "jsonVersion" : 2, + "outcome" : "success", + "version" : "355.7.7" + }, + "result" : { + "devices" : [ + { + "capabilities" : [ + { + "featureIdentifier" : "com.apple.coredevice.feature.connectdevice", + "name" : "Connect to Device" + }, + { + "featureIdentifier" : "com.apple.coredevice.feature.acquireusageassertion", + "name" : "Acquire Usage Assertion" + }, + { + "featureIdentifier" : "com.apple.coredevice.feature.unpairdevice", + "name" : "Unpair Device" + } + ], + "connectionProperties" : { + "authenticationType" : "manualPairing", + "isMobileDeviceOnly" : false, + "lastConnectionDate" : "2024-01-29T08:49:25.179Z", + "pairingState" : "paired", + "potentialHostnames" : [ + "00000000-0000000000000000.coredevice.local", + "00000000-0000-0000-0000-000000000000.coredevice.local" + ], + "transportType" : "wired", + "tunnelState" : "disconnected", + "tunnelTransportProtocol" : "tcp" + }, + "deviceProperties" : { + "bootedFromSnapshot" : true, + "bootedSnapshotName" : "com.apple.os.update-0000", + "ddiServicesAvailable" : false, + "developerModeStatus" : "enabled", + "hasInternalOSBuild" : false, + "name" : "Some iOS device", + "osBuildUpdate" : "21D50", + "osVersionNumber" : "17.3", + "rootFileSystemIsWritable" : false + }, + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + "deviceType" : "iPad", + "ecid" : 0, + "hardwareModel" : "J211AP", + "internalStorageCapacity" : 64000000000, + "isProductionFused" : true, + "marketingName" : "iPad mini (5th generation)", + "platform" : "iOS", + "productType" : "iPad11,2", + "reality" : "physical", + "serialNumber" : "000000000000", + "supportedCPUTypes" : [ + { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 0, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 1, + "type" : 16777228 + }, + { + "name" : "arm64_32", + "subType" : 1, + "type" : 33554444 + } + ], + "supportedDeviceFamilies" : [ + 1, + 2 + ], + "thinningProductType" : "iPad11,2", + "udid" : "00000000-0000000000000000" + }, + "identifier" : "00000000-0000-0000-0000-000000000000", + "visibilityClass" : "default" + } + ] + } +})raw"); + + QTest::addRow("handled device") + << data << QString("000000000000000000000000") << QString() + << QMap({{"cpuArchitecture", "arm64e"}, + {"developerStatus", "Development"}, + {"deviceConnected", "YES"}, + {"deviceName", "Some iOS device"}, + {"osVersion", "17.3 (21D50)"}, + {"uniqueDeviceId", "00000000-0000000000000000"}}); + QTest::addRow("unhandled device") + << data << QString("000000000000000000000001") + << QString("Device is not handled by devicectl") << QMap({}); +} + +void tst_Devicectlutils::parseDeviceInfo() +{ + using InfoMap = QMap; + QFETCH(QByteArray, data); + QFETCH(QString, usbId); + QFETCH(QString, error); + QFETCH(InfoMap, info); + + const Utils::expected_str result = Ios::Internal::parseDeviceInfo(data, usbId); + if (error.isEmpty()) { + QVERIFY(result); + QCOMPARE(*result, info); + } else { + QVERIFY(!result); + QCOMPARE(result.error(), error); + } +} + QTEST_GUILESS_MAIN(tst_Devicectlutils) #include "tst_devicectlutils.moc"