2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
+ /// @docImport '../resident_runner.dart';
6
+ library ;
7
+
5
8
import 'dart:async' ;
6
9
7
10
import 'package:meta/meta.dart' ;
@@ -28,6 +31,7 @@ import '../device_vm_service_discovery_for_attach.dart';
28
31
import '../features.dart' ;
29
32
import '../globals.dart' as globals;
30
33
import '../macos/xcdevice.dart' ;
34
+ import '../macos/xcode.dart' ;
31
35
import '../mdns_discovery.dart' ;
32
36
import '../project.dart' ;
33
37
import '../protocol_discovery.dart' ;
@@ -37,6 +41,7 @@ import 'core_devices.dart';
37
41
import 'ios_deploy.dart' ;
38
42
import 'ios_workflow.dart' ;
39
43
import 'iproxy.dart' ;
44
+ import 'lldb.dart' ;
40
45
import 'mac.dart' ;
41
46
import 'xcode_build_settings.dart' ;
42
47
import 'xcode_debug.dart' ;
@@ -414,8 +419,11 @@ class IOSDevice extends Device {
414
419
int installationResult;
415
420
try {
416
421
if (isCoreDevice) {
417
- installationResult =
418
- await _coreDeviceControl.installApp (deviceId: id, bundlePath: bundle.path) ? 0 : 1 ;
422
+ final (bool installSuccess, _) = await _coreDeviceControl.installApp (
423
+ deviceId: id,
424
+ bundlePath: bundle.path,
425
+ );
426
+ installationResult = installSuccess ? 0 : 1 ;
419
427
} else {
420
428
installationResult = await _iosDeploy.installApp (
421
429
deviceId: id,
@@ -964,7 +972,7 @@ class IOSDevice extends Device {
964
972
// Release mode
965
973
966
974
// Install app to device
967
- final bool installSuccess = await _coreDeviceControl.installApp (
975
+ final ( bool installSuccess, _) = await _coreDeviceControl.installApp (
968
976
deviceId: id,
969
977
bundlePath: package.deviceBundlePath,
970
978
);
@@ -991,6 +999,14 @@ class IOSDevice extends Device {
991
999
final Version ? xcodeVersion = globals.xcode? .currentVersion;
992
1000
final bool lldbFeatureEnabled = featureFlags.isLLDBDebuggingEnabled;
993
1001
if (xcodeVersion != null && xcodeVersion.major >= 26 && lldbFeatureEnabled) {
1002
+ final DeviceLogReader deviceLogReader = getLogReader (
1003
+ app: package,
1004
+ usingCISystem: debuggingOptions.usingCISystem,
1005
+ );
1006
+ if (deviceLogReader is IOSDeviceLogReader ) {
1007
+ await deviceLogReader.listenToCoreDeviceLauncher (_coreDeviceLauncher);
1008
+ }
1009
+
994
1010
final bool launchSuccess = await _coreDeviceLauncher.launchAppWithLLDBDebugger (
995
1011
deviceId: id,
996
1012
bundlePath: package.deviceBundlePath,
@@ -1138,6 +1154,7 @@ class IOSDevice extends Device {
1138
1154
app: app,
1139
1155
iMobileDevice: _iMobileDevice,
1140
1156
usingCISystem: usingCISystem,
1157
+ xcode: globals.xcode,
1141
1158
),
1142
1159
);
1143
1160
}
@@ -1291,8 +1308,21 @@ String decodeSyslog(String line) {
1291
1308
}
1292
1309
}
1293
1310
1311
+ /// Listens to multiple logging sources to get the logs from the physical iOS device.
1312
+ ///
1313
+ /// Potential logging sources include:
1314
+ /// * `idevicesyslog`
1315
+ /// * `ios-deploy`
1316
+ /// * `devicectl` and `lldb`
1317
+ /// * Unified Logging (Dart VM)
1318
+ ///
1319
+ /// Not all logging sources work on all devices. See [logSources] for limitations.
1320
+ ///
1321
+ /// Logs are added to the [linesController] and consumed through the [logLines] stream by
1322
+ /// [FlutterDevice.startEchoingDeviceLog] .
1294
1323
class IOSDeviceLogReader extends DeviceLogReader {
1295
1324
IOSDeviceLogReader ._(
1325
+ this ._xcode,
1296
1326
this ._iMobileDevice,
1297
1327
this ._majorSdkVersion,
1298
1328
this ._deviceId,
@@ -1313,10 +1343,12 @@ class IOSDeviceLogReader extends DeviceLogReader {
1313
1343
required IOSDevice device,
1314
1344
IOSApp ? app,
1315
1345
required IMobileDevice iMobileDevice,
1346
+ required Xcode ? xcode,
1316
1347
bool usingCISystem = false ,
1317
1348
}) {
1318
1349
final String appName = app? .name? .replaceAll ('.app' , '' ) ?? '' ;
1319
1350
return IOSDeviceLogReader ._(
1351
+ xcode,
1320
1352
iMobileDevice,
1321
1353
device.majorSdkVersion,
1322
1354
device.id,
@@ -1331,6 +1363,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
1331
1363
/// Create an [IOSDeviceLogReader] for testing.
1332
1364
factory IOSDeviceLogReader .test ({
1333
1365
required IMobileDevice iMobileDevice,
1366
+ required Xcode ? xcode,
1334
1367
bool useSyslog = true ,
1335
1368
bool usingCISystem = false ,
1336
1369
int ? majorSdkVersion,
@@ -1339,6 +1372,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
1339
1372
}) {
1340
1373
final int sdkVersion = majorSdkVersion ?? (useSyslog ? 12 : 13 );
1341
1374
return IOSDeviceLogReader ._(
1375
+ xcode,
1342
1376
iMobileDevice,
1343
1377
sdkVersion,
1344
1378
'1234' ,
@@ -1357,6 +1391,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
1357
1391
final bool _isWirelesslyConnected;
1358
1392
final bool _isCoreDevice;
1359
1393
final IMobileDevice _iMobileDevice;
1394
+ final Xcode ? _xcode;
1360
1395
final bool _usingCISystem;
1361
1396
1362
1397
// Matches a syslog line from the runner.
@@ -1448,6 +1483,15 @@ class IOSDeviceLogReader extends DeviceLogReader {
1448
1483
@override
1449
1484
Stream <String > get logLines => linesController.stream;
1450
1485
1486
+ final _coreDeviceLoggingSource = CoreDeviceLoggingSource ();
1487
+ Future <void > listenToCoreDeviceLauncher (IOSCoreDeviceLauncher launcher) async {
1488
+ if (! useCoreDeviceLogging) {
1489
+ return ;
1490
+ }
1491
+ _coreDeviceLoggingSource.coreDeviceLauncher = launcher;
1492
+ await _coreDeviceLoggingSource.listenToLogs (addToLinesController, linesController);
1493
+ }
1494
+
1451
1495
FlutterVmService ? _connectedVmService;
1452
1496
1453
1497
@override
@@ -1469,6 +1513,15 @@ class IOSDeviceLogReader extends DeviceLogReader {
1469
1513
// Also, `idevicesyslog` does not work with iOS 17 wireless devices, so use the
1470
1514
// Dart VM for wireless devices.
1471
1515
if (_isCoreDevice) {
1516
+ // `idevicesyslog` stopped working with at least Xcode 26 (may have been before).
1517
+ // Instead, use logging from `devicectl` and `lldb`.
1518
+ final Version ? xcodeVersion = _xcode? .currentVersion;
1519
+ if (xcodeVersion != null && xcodeVersion.major >= 26 ) {
1520
+ return _IOSDeviceLogSources (
1521
+ primarySource: IOSDeviceLogSource .devicectlAndLldb,
1522
+ fallbackSource: IOSDeviceLogSource .unifiedLogging,
1523
+ );
1524
+ }
1472
1525
if (_isWirelesslyConnected) {
1473
1526
return _IOSDeviceLogSources (primarySource: IOSDeviceLogSource .unifiedLogging);
1474
1527
}
@@ -1536,6 +1589,13 @@ class IOSDeviceLogReader extends DeviceLogReader {
1536
1589
logSources.fallbackSource == IOSDeviceLogSource .iosDeploy;
1537
1590
}
1538
1591
1592
+ /// Whether `devicectl` and `lldb` are used as the primary or fallback source for device logs.
1593
+ @visibleForTesting
1594
+ bool get useCoreDeviceLogging {
1595
+ return logSources.primarySource == IOSDeviceLogSource .devicectlAndLldb ||
1596
+ logSources.fallbackSource == IOSDeviceLogSource .devicectlAndLldb;
1597
+ }
1598
+
1539
1599
/// Listen to Dart VM for logs on iOS 13 or greater.
1540
1600
Future <void > _listenToUnifiedLoggingEvents (FlutterVmService connectedVmService) async {
1541
1601
if (! useUnifiedLogging) {
@@ -1671,6 +1731,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
1671
1731
}
1672
1732
idevicesyslogProcess? .kill ();
1673
1733
_iosDeployDebugger? .detach ();
1734
+ _coreDeviceLoggingSource.dispose ();
1674
1735
}
1675
1736
}
1676
1737
@@ -1683,6 +1744,9 @@ enum IOSDeviceLogSource {
1683
1744
1684
1745
/// Gets logs from the Dart VM Service.
1685
1746
unifiedLogging,
1747
+
1748
+ /// Gets logs from `devicectl` and `lldb`
1749
+ devicectlAndLldb,
1686
1750
}
1687
1751
1688
1752
class _IOSDeviceLogSources {
@@ -1692,6 +1756,62 @@ class _IOSDeviceLogSources {
1692
1756
final IOSDeviceLogSource ? fallbackSource;
1693
1757
}
1694
1758
1759
+ @visibleForTesting
1760
+ class CoreDeviceLoggingSource {
1761
+ IOSCoreDeviceLauncher ? coreDeviceLauncher;
1762
+ final _loggingSubscriptions = < StreamSubscription <void >> [];
1763
+
1764
+ Future <void > listenToLogs (
1765
+ void Function (String , IOSDeviceLogSource ) onLogMessage,
1766
+ StreamController <String > linesController,
1767
+ ) async {
1768
+ final IOSCoreDeviceLogForwarder ? debugger = coreDeviceLauncher? .coreDeviceLogForwarder;
1769
+ if (debugger != null ) {
1770
+ _loggingSubscriptions.add (
1771
+ debugger.logLines.listen (
1772
+ (String line) =>
1773
+ onLogMessage (_debuggerLineHandler (line), IOSDeviceLogSource .devicectlAndLldb),
1774
+ onError: linesController.addError,
1775
+ onDone: linesController.close,
1776
+ cancelOnError: true ,
1777
+ ),
1778
+ );
1779
+ }
1780
+
1781
+ final LLDBLogForwarder ? lldbLogForwarder = coreDeviceLauncher? .lldbLogForwarder;
1782
+ if (lldbLogForwarder != null ) {
1783
+ _loggingSubscriptions.add (
1784
+ lldbLogForwarder.logLines.listen (
1785
+ (String line) =>
1786
+ onLogMessage (_debuggerLineHandler (line), IOSDeviceLogSource .devicectlAndLldb),
1787
+ onError: linesController.addError,
1788
+ onDone: linesController.close,
1789
+ cancelOnError: true ,
1790
+ ),
1791
+ );
1792
+ }
1793
+ }
1794
+
1795
+ // Logging from native code/Flutter engine is prefixed by timestamp and process metadata:
1796
+ // 2020-09-15 19:15:10.931434-0700 Runner[541:226276] Did finish launching.
1797
+ // 2020-09-15 19:15:10.931434-0700 Runner[541:226276] [Category] Did finish launching.
1798
+ //
1799
+ // Logging from the dart code has no prefixing metadata.
1800
+ final _debuggerLoggingRegex = RegExp (r'^\S* \S* \S*\[[0-9:]*] (.*)' );
1801
+
1802
+ // Strip off the logging metadata (leave the category), or just echo the line.
1803
+ String _debuggerLineHandler (String line) =>
1804
+ _debuggerLoggingRegex.firstMatch (line)? .group (1 ) ?? line;
1805
+
1806
+ void dispose () {
1807
+ for (final StreamSubscription <void > loggingSubscription in _loggingSubscriptions) {
1808
+ loggingSubscription.cancel ();
1809
+ }
1810
+ coreDeviceLauncher? .lldbLogForwarder.exit ();
1811
+ coreDeviceLauncher? .coreDeviceLogForwarder.exit ();
1812
+ }
1813
+ }
1814
+
1695
1815
/// A [DevicePortForwarder] specialized for iOS usage with iproxy.
1696
1816
class IOSDevicePortForwarder extends DevicePortForwarder {
1697
1817
/// Create a new [IOSDevicePortForwarder] .
0 commit comments