Unverified Commit d631b262 authored by Victoria Ashworth's avatar Victoria Ashworth Committed by GitHub

New tooling for iOS 17 physical devices (#131865)

This PR includes the following changes. These changes only apply to iOS 17 physical devices.

| Command | Change Description  | Changes to User Experience |
| ------------- | ------------- | ------------- |
| `flutter run --release` | Uses `devicectl` to install and launch application in release mode.  | No change.  |
| `flutter run`  | Uses Xcode via automation scripting to run application in debug and profile mode. | Xcode will be opened in the background. Errors/crashes may be caught in Xcode and therefore may not show in terminal. |
| `flutter run --use-application-binary=xxxx` | Creates temporary empty Xcode project and use Xcode to run via automation scripting in debug and profile. | Xcode will be opened in the background. Errors/crashes may be caught in Xcode and therefore may not show in terminal.  |
| `flutter install` | Uses `devicectl` to check installed apps, install app, uninstall app.  | No change.  |
| `flutter screenshot` | Will return error.  | Will return error.  |

Other changes include:
* Using `devicectl` to get information about the device
* Using `idevicesyslog` and Dart VM logging for device logs

Note:
Xcode automation scripting (used in `flutter run` for debug and profile) does not work in a headless (without a UI) interface. No known workaround.

Fixes https://github.com/flutter/flutter/issues/128827, https://github.com/flutter/flutter/issues/128531.
parent 88ed9bd9
...@@ -3685,6 +3685,16 @@ targets: ...@@ -3685,6 +3685,16 @@ targets:
["devicelab", "ios", "mac"] ["devicelab", "ios", "mac"]
task_name: flutter_gallery_ios__start_up task_name: flutter_gallery_ios__start_up
- name: Mac_ios flutter_gallery_ios__start_up_xcode_debug
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab", "ios", "mac"]
task_name: flutter_gallery_ios__start_up_xcode_debug
bringup: true
- name: Mac_ios flutter_view_ios__start_up - name: Mac_ios flutter_view_ios__start_up
recipe: devicelab/devicelab_drone recipe: devicelab/devicelab_drone
presubmit: false presubmit: false
...@@ -3752,6 +3762,16 @@ targets: ...@@ -3752,6 +3762,16 @@ targets:
["devicelab", "ios", "mac"] ["devicelab", "ios", "mac"]
task_name: integration_ui_ios_driver task_name: integration_ui_ios_driver
- name: Mac_ios integration_ui_ios_driver_xcode_debug
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab", "ios", "mac"]
task_name: integration_ui_ios_driver_xcode_debug
bringup: true
- name: Mac_ios integration_ui_ios_frame_number - name: Mac_ios integration_ui_ios_frame_number
recipe: devicelab/devicelab_drone recipe: devicelab/devicelab_drone
presubmit: false presubmit: false
......
...@@ -168,6 +168,7 @@ ...@@ -168,6 +168,7 @@
/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @vashworth @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @vashworth @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @vashworth @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @vashworth @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up_xcode_debug.dart @vashworth @flutter/engine
/dev/devicelab/bin/tasks/flutter_gallery_ios_sksl_warmup__transition_perf.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios_sksl_warmup__transition_perf.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/flutter_view_ios__start_up.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flutter_view_ios__start_up.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine
...@@ -178,6 +179,7 @@ ...@@ -178,6 +179,7 @@
/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine
/dev/devicelab/bin/tasks/integration_test_test_ios.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/integration_test_test_ios.dart @cyanglaz @flutter/engine
/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart @cyanglaz @flutter/tool /dev/devicelab/bin/tasks/integration_ui_ios_driver.dart @cyanglaz @flutter/tool
/dev/devicelab/bin/tasks/integration_ui_ios_driver_xcode_debug.dart @vashworth @flutter/tool
/dev/devicelab/bin/tasks/integration_ui_ios_frame_number.dart @iskakaushik @flutter/engine /dev/devicelab/bin/tasks/integration_ui_ios_frame_number.dart @iskakaushik @flutter/engine
/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart @cyanglaz @flutter/engine
/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart @cyanglaz @flutter/tool /dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart @cyanglaz @flutter/tool
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
// TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128)
// XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use
// FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug
// workflow in CI to test from older versions since devicelab has not yet been
// updated to iOS 17 and Xcode 15.
deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createFlutterGalleryStartupTest(
runEnvironment: <String, String>{
'FORCE_XCODE_DEBUG': 'true',
},
));
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<void> main() async {
// TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128)
// XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use
// FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug
// workflow in CI to test from older versions since devicelab has not yet been
// updated to iOS 17 and Xcode 15.
deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createEndToEndDriverTest(
environment: <String, String>{
'FORCE_XCODE_DEBUG': 'true',
},
));
}
...@@ -106,10 +106,11 @@ TaskFunction createEndToEndFrameNumberTest() { ...@@ -106,10 +106,11 @@ TaskFunction createEndToEndFrameNumberTest() {
).call; ).call;
} }
TaskFunction createEndToEndDriverTest() { TaskFunction createEndToEndDriverTest({Map<String, String>? environment}) {
return DriverTest( return DriverTest(
'${flutterDirectory.path}/dev/integration_tests/ui', '${flutterDirectory.path}/dev/integration_tests/ui',
'lib/driver.dart', 'lib/driver.dart',
environment: environment,
).call; ).call;
} }
...@@ -173,6 +174,7 @@ class DriverTest { ...@@ -173,6 +174,7 @@ class DriverTest {
this.testTarget, { this.testTarget, {
this.extraOptions = const <String>[], this.extraOptions = const <String>[],
this.deviceIdOverride, this.deviceIdOverride,
this.environment,
} }
); );
...@@ -180,6 +182,7 @@ class DriverTest { ...@@ -180,6 +182,7 @@ class DriverTest {
final String testTarget; final String testTarget;
final List<String> extraOptions; final List<String> extraOptions;
final String? deviceIdOverride; final String? deviceIdOverride;
final Map<String, String>? environment;
Future<TaskResult> call() { Future<TaskResult> call() {
return inDirectory<TaskResult>(testDirectory, () async { return inDirectory<TaskResult>(testDirectory, () async {
...@@ -202,7 +205,7 @@ class DriverTest { ...@@ -202,7 +205,7 @@ class DriverTest {
deviceId, deviceId,
...extraOptions, ...extraOptions,
]; ];
await flutter('drive', options: options); await flutter('drive', options: options, environment: environment);
return TaskResult.success(null); return TaskResult.success(null);
}); });
......
...@@ -233,10 +233,11 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) { ...@@ -233,10 +233,11 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) {
).run; ).run;
} }
TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart'}) { TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart', Map<String, String>? runEnvironment}) {
return StartupTest( return StartupTest(
'${flutterDirectory.path}/dev/integration_tests/flutter_gallery', '${flutterDirectory.path}/dev/integration_tests/flutter_gallery',
target: target, target: target,
runEnvironment: runEnvironment,
).run; ).run;
} }
...@@ -768,11 +769,17 @@ Future<void> _resetManifest(String testDirectory) async { ...@@ -768,11 +769,17 @@ Future<void> _resetManifest(String testDirectory) async {
/// Measure application startup performance. /// Measure application startup performance.
class StartupTest { class StartupTest {
const StartupTest(this.testDirectory, { this.reportMetrics = true, this.target = 'lib/main.dart' }); const StartupTest(
this.testDirectory, {
this.reportMetrics = true,
this.target = 'lib/main.dart',
this.runEnvironment,
});
final String testDirectory; final String testDirectory;
final bool reportMetrics; final bool reportMetrics;
final String target; final String target;
final Map<String, String>? runEnvironment;
Future<TaskResult> run() async { Future<TaskResult> run() async {
return inDirectory<TaskResult>(testDirectory, () async { return inDirectory<TaskResult>(testDirectory, () async {
...@@ -855,21 +862,26 @@ class StartupTest { ...@@ -855,21 +862,26 @@ class StartupTest {
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png', 'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
); );
}); });
final int result = await flutter('run', options: <String>[ final int result = await flutter(
'--no-android-gradle-daemon', 'run',
'--no-publish-port', options: <String>[
'--verbose', '--no-android-gradle-daemon',
'--profile', '--no-publish-port',
'--trace-startup', '--verbose',
// TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836 '--profile',
if (device is IosDevice) '--trace-startup',
'--verbose-system-logs', // TODO(vashworth): Remove once done debugging https://github.com/flutter/flutter/issues/129836
'--target=$target', if (device is IosDevice)
'-d', '--verbose-system-logs',
device.deviceId, '--target=$target',
if (applicationBinaryPath != null) '-d',
'--use-application-binary=$applicationBinaryPath', device.deviceId,
], canFail: true); if (applicationBinaryPath != null)
'--use-application-binary=$applicationBinaryPath',
],
environment: runEnvironment,
canFail: true,
);
timer.cancel(); timer.cancel();
if (result == 0) { if (result == 0) {
final Map<String, dynamic> data = json.decode( final Map<String, dynamic> data = json.decode(
......
This diff is collapsed.
...@@ -359,6 +359,7 @@ Future<T> runInContext<T>( ...@@ -359,6 +359,7 @@ Future<T> runInContext<T>(
platform: globals.platform, platform: globals.platform,
fileSystem: globals.fs, fileSystem: globals.fs,
xcodeProjectInterpreter: globals.xcodeProjectInterpreter!, xcodeProjectInterpreter: globals.xcodeProjectInterpreter!,
userMessages: globals.userMessages,
), ),
XCDevice: () => XCDevice( XCDevice: () => XCDevice(
processManager: globals.processManager, processManager: globals.processManager,
...@@ -375,6 +376,7 @@ Future<T> runInContext<T>( ...@@ -375,6 +376,7 @@ Future<T> runInContext<T>(
processManager: globals.processManager, processManager: globals.processManager,
dyLdLibEntry: globals.cache.dyLdLibEntry, dyLdLibEntry: globals.cache.dyLdLibEntry,
), ),
fileSystem: globals.fs,
), ),
XcodeProjectInterpreter: () => XcodeProjectInterpreter( XcodeProjectInterpreter: () => XcodeProjectInterpreter(
logger: globals.logger, logger: globals.logger,
......
...@@ -1159,6 +1159,7 @@ class DebuggingOptions { ...@@ -1159,6 +1159,7 @@ class DebuggingOptions {
Map<String, Object?> platformArgs, { Map<String, Object?> platformArgs, {
bool ipv6 = false, bool ipv6 = false,
DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached, DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached,
bool isCoreDevice = false,
}) { }) {
final String dartVmFlags = computeDartVmFlags(this); final String dartVmFlags = computeDartVmFlags(this);
return <String>[ return <String>[
...@@ -1172,7 +1173,10 @@ class DebuggingOptions { ...@@ -1172,7 +1173,10 @@ class DebuggingOptions {
if (environmentType == EnvironmentType.simulator && dartVmFlags.isNotEmpty) if (environmentType == EnvironmentType.simulator && dartVmFlags.isNotEmpty)
'--dart-flags=$dartVmFlags', '--dart-flags=$dartVmFlags',
if (useTestFonts) '--use-test-fonts', if (useTestFonts) '--use-test-fonts',
if (debuggingEnabled) ...<String>[ // Core Devices (iOS 17 devices) are debugged through Xcode so don't
// include these flags, which are used to check if the app was launched
// via Flutter CLI and `ios-deploy`.
if (debuggingEnabled && !isCoreDevice) ...<String>[
'--enable-checked-mode', '--enable-checked-mode',
'--verify-entry-points', '--verify-entry-points',
], ],
......
This diff is collapsed.
...@@ -132,6 +132,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -132,6 +132,7 @@ Future<XcodeBuildResult> buildXcodeProject({
DarwinArch? activeArch, DarwinArch? activeArch,
bool codesign = true, bool codesign = true,
String? deviceID, String? deviceID,
bool isCoreDevice = false,
bool configOnly = false, bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build, XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async { }) async {
...@@ -240,6 +241,7 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -240,6 +241,7 @@ Future<XcodeBuildResult> buildXcodeProject({
project: project, project: project,
targetOverride: targetOverride, targetOverride: targetOverride,
buildInfo: buildInfo, buildInfo: buildInfo,
usingCoreDevice: isCoreDevice,
); );
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode); await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) { if (configOnly) {
......
...@@ -35,6 +35,7 @@ Future<void> updateGeneratedXcodeProperties({ ...@@ -35,6 +35,7 @@ Future<void> updateGeneratedXcodeProperties({
String? targetOverride, String? targetOverride,
bool useMacOSConfig = false, bool useMacOSConfig = false,
String? buildDirOverride, String? buildDirOverride,
bool usingCoreDevice = false,
}) async { }) async {
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines( final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
project: project, project: project,
...@@ -42,6 +43,7 @@ Future<void> updateGeneratedXcodeProperties({ ...@@ -42,6 +43,7 @@ Future<void> updateGeneratedXcodeProperties({
targetOverride: targetOverride, targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig, useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride, buildDirOverride: buildDirOverride,
usingCoreDevice: usingCoreDevice,
); );
_updateGeneratedXcodePropertiesFile( _updateGeneratedXcodePropertiesFile(
...@@ -143,6 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({ ...@@ -143,6 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({
String? targetOverride, String? targetOverride,
bool useMacOSConfig = false, bool useMacOSConfig = false,
String? buildDirOverride, String? buildDirOverride,
bool usingCoreDevice = false,
}) async { }) async {
final List<String> xcodeBuildSettings = <String>[]; final List<String> xcodeBuildSettings = <String>[];
...@@ -170,6 +173,12 @@ Future<List<String>> _xcodeBuildSettingsLines({ ...@@ -170,6 +173,12 @@ Future<List<String>> _xcodeBuildSettingsLines({
final String buildNumber = parsedBuildNumber(manifest: project.manifest, buildInfo: buildInfo) ?? '1'; final String buildNumber = parsedBuildNumber(manifest: project.manifest, buildInfo: buildInfo) ?? '1';
xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber'); xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
// CoreDevices in debug and profile mode are launched, but not built, via Xcode.
// Set the BUILD_DIR so Xcode knows where to find the app bundle to launch.
if (usingCoreDevice && !buildInfo.isRelease) {
xcodeBuildSettings.add('BUILD_DIR=${globals.fs.path.absolute(getIosBuildDirectory())}');
}
final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo; final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo;
if (localEngineInfo != null) { if (localEngineInfo != null) {
final String engineOutPath = localEngineInfo.engineOutPath; final String engineOutPath = localEngineInfo.engineOutPath;
......
This diff is collapsed.
...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; ...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
...@@ -18,10 +19,12 @@ import '../cache.dart'; ...@@ -18,10 +19,12 @@ import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/core_devices.dart';
import '../ios/devices.dart'; import '../ios/devices.dart';
import '../ios/ios_deploy.dart'; import '../ios/ios_deploy.dart';
import '../ios/iproxy.dart'; import '../ios/iproxy.dart';
import '../ios/mac.dart'; import '../ios/mac.dart';
import '../ios/xcode_debug.dart';
import '../reporting/reporting.dart'; import '../reporting/reporting.dart';
import 'xcode.dart'; import 'xcode.dart';
...@@ -65,6 +68,10 @@ class XCDevice { ...@@ -65,6 +68,10 @@ class XCDevice {
required Xcode xcode, required Xcode xcode,
required Platform platform, required Platform platform,
required IProxy iproxy, required IProxy iproxy,
required FileSystem fileSystem,
@visibleForTesting
IOSCoreDeviceControl? coreDeviceControl,
XcodeDebug? xcodeDebug,
}) : _processUtils = ProcessUtils(logger: logger, processManager: processManager), }) : _processUtils = ProcessUtils(logger: logger, processManager: processManager),
_logger = logger, _logger = logger,
_iMobileDevice = IMobileDevice( _iMobileDevice = IMobileDevice(
...@@ -80,6 +87,18 @@ class XCDevice { ...@@ -80,6 +87,18 @@ class XCDevice {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
), ),
_coreDeviceControl = coreDeviceControl ?? IOSCoreDeviceControl(
logger: logger,
processManager: processManager,
xcode: xcode,
fileSystem: fileSystem,
),
_xcodeDebug = xcodeDebug ?? XcodeDebug(
logger: logger,
processManager: processManager,
xcode: xcode,
fileSystem: fileSystem,
),
_iProxy = iproxy, _iProxy = iproxy,
_xcode = xcode { _xcode = xcode {
...@@ -99,6 +118,8 @@ class XCDevice { ...@@ -99,6 +118,8 @@ class XCDevice {
final IOSDeploy _iosDeploy; final IOSDeploy _iosDeploy;
final Xcode _xcode; final Xcode _xcode;
final IProxy _iProxy; final IProxy _iProxy;
final IOSCoreDeviceControl _coreDeviceControl;
final XcodeDebug _xcodeDebug;
List<Object>? _cachedListResults; List<Object>? _cachedListResults;
...@@ -457,6 +478,17 @@ class XCDevice { ...@@ -457,6 +478,17 @@ class XCDevice {
return const <IOSDevice>[]; return const <IOSDevice>[];
} }
final Map<String, IOSCoreDevice> coreDeviceMap = <String, IOSCoreDevice>{};
if (_xcode.isDevicectlInstalled) {
final List<IOSCoreDevice> coreDevices = await _coreDeviceControl.getCoreDevices();
for (final IOSCoreDevice device in coreDevices) {
if (device.udid == null) {
continue;
}
coreDeviceMap[device.udid!] = device;
}
}
// [ // [
// { // {
// "simulator" : true, // "simulator" : true,
...@@ -565,11 +597,27 @@ class XCDevice { ...@@ -565,11 +597,27 @@ class XCDevice {
} }
} }
DeviceConnectionInterface connectionInterface = _interfaceType(device);
// CoreDevices (devices with iOS 17 and greater) no longer reflect the
// correct connection interface or developer mode status in `xcdevice`.
// Use `devicectl` to get that information for CoreDevices.
final IOSCoreDevice? coreDevice = coreDeviceMap[identifier];
if (coreDevice != null) {
if (coreDevice.connectionInterface != null) {
connectionInterface = coreDevice.connectionInterface!;
}
if (coreDevice.deviceProperties?.developerModeStatus != 'enabled') {
devModeEnabled = false;
}
}
deviceMap[identifier] = IOSDevice( deviceMap[identifier] = IOSDevice(
identifier, identifier,
name: name, name: name,
cpuArchitecture: _cpuArchitecture(device), cpuArchitecture: _cpuArchitecture(device),
connectionInterface: _interfaceType(device), connectionInterface: connectionInterface,
isConnected: isConnected, isConnected: isConnected,
sdkVersion: sdkVersionString, sdkVersion: sdkVersionString,
iProxy: _iProxy, iProxy: _iProxy,
...@@ -577,8 +625,11 @@ class XCDevice { ...@@ -577,8 +625,11 @@ class XCDevice {
logger: _logger, logger: _logger,
iosDeploy: _iosDeploy, iosDeploy: _iosDeploy,
iMobileDevice: _iMobileDevice, iMobileDevice: _iMobileDevice,
coreDeviceControl: _coreDeviceControl,
xcodeDebug: _xcodeDebug,
platform: globals.platform, platform: globals.platform,
devModeEnabled: devModeEnabled, devModeEnabled: devModeEnabled,
isCoreDevice: coreDevice != null,
); );
} }
} }
......
...@@ -14,8 +14,10 @@ import '../base/io.dart'; ...@@ -14,8 +14,10 @@ import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/user_messages.dart';
import '../base/version.dart'; import '../base/version.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart';
import '../ios/xcodeproj.dart'; import '../ios/xcodeproj.dart';
Version get xcodeRequiredVersion => Version(14, null, null); Version get xcodeRequiredVersion => Version(14, null, null);
...@@ -44,9 +46,13 @@ class Xcode { ...@@ -44,9 +46,13 @@ class Xcode {
required Logger logger, required Logger logger,
required FileSystem fileSystem, required FileSystem fileSystem,
required XcodeProjectInterpreter xcodeProjectInterpreter, required XcodeProjectInterpreter xcodeProjectInterpreter,
required UserMessages userMessages,
String? flutterRoot,
}) : _platform = platform, }) : _platform = platform,
_fileSystem = fileSystem, _fileSystem = fileSystem,
_xcodeProjectInterpreter = xcodeProjectInterpreter, _xcodeProjectInterpreter = xcodeProjectInterpreter,
_userMessage = userMessages,
_flutterRoot = flutterRoot,
_processUtils = _processUtils =
ProcessUtils(logger: logger, processManager: processManager), ProcessUtils(logger: logger, processManager: processManager),
_logger = logger; _logger = logger;
...@@ -61,6 +67,7 @@ class Xcode { ...@@ -61,6 +67,7 @@ class Xcode {
XcodeProjectInterpreter? xcodeProjectInterpreter, XcodeProjectInterpreter? xcodeProjectInterpreter,
Platform? platform, Platform? platform,
FileSystem? fileSystem, FileSystem? fileSystem,
String? flutterRoot,
Logger? logger, Logger? logger,
}) { }) {
platform ??= FakePlatform( platform ??= FakePlatform(
...@@ -72,6 +79,8 @@ class Xcode { ...@@ -72,6 +79,8 @@ class Xcode {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
fileSystem: fileSystem ?? MemoryFileSystem.test(), fileSystem: fileSystem ?? MemoryFileSystem.test(),
userMessages: UserMessages(),
flutterRoot: flutterRoot,
logger: logger, logger: logger,
xcodeProjectInterpreter: xcodeProjectInterpreter ?? XcodeProjectInterpreter.test(processManager: processManager), xcodeProjectInterpreter: xcodeProjectInterpreter ?? XcodeProjectInterpreter.test(processManager: processManager),
); );
...@@ -81,6 +90,8 @@ class Xcode { ...@@ -81,6 +90,8 @@ class Xcode {
final ProcessUtils _processUtils; final ProcessUtils _processUtils;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final XcodeProjectInterpreter _xcodeProjectInterpreter; final XcodeProjectInterpreter _xcodeProjectInterpreter;
final UserMessages _userMessage;
final String? _flutterRoot;
final Logger _logger; final Logger _logger;
bool get isInstalledAndMeetsVersionCheck => _platform.isMacOS && isInstalled && isRequiredVersionSatisfactory; bool get isInstalledAndMeetsVersionCheck => _platform.isMacOS && isInstalled && isRequiredVersionSatisfactory;
...@@ -101,6 +112,38 @@ class Xcode { ...@@ -101,6 +112,38 @@ class Xcode {
return _xcodeSelectPath; return _xcodeSelectPath;
} }
String get xcodeAppPath {
// If the Xcode Select Path is /Applications/Xcode.app/Contents/Developer,
// the path to Xcode App is /Applications/Xcode.app
final String? pathToXcode = xcodeSelectPath;
if (pathToXcode == null || pathToXcode.isEmpty) {
throwToolExit(_userMessage.xcodeMissing);
}
final int index = pathToXcode.indexOf('.app');
if (index == -1) {
throwToolExit(_userMessage.xcodeMissing);
}
return pathToXcode.substring(0, index + 4);
}
/// Path to script to automate debugging through Xcode. Used in xcode_debug.dart.
/// Located in this file to make it easily overrideable in google3.
String get xcodeAutomationScriptPath {
final String flutterRoot = _flutterRoot ?? Cache.flutterRoot!;
final String flutterToolsAbsolutePath = _fileSystem.path.join(
flutterRoot,
'packages',
'flutter_tools',
);
final String filePath = '$flutterToolsAbsolutePath/bin/xcode_debug.js';
if (!_fileSystem.file(filePath).existsSync()) {
throwToolExit('Unable to find Xcode automation script at $filePath');
}
return filePath;
}
bool get isInstalled => _xcodeProjectInterpreter.isInstalled; bool get isInstalled => _xcodeProjectInterpreter.isInstalled;
Version? get currentVersion => _xcodeProjectInterpreter.version; Version? get currentVersion => _xcodeProjectInterpreter.version;
...@@ -150,6 +193,28 @@ class Xcode { ...@@ -150,6 +193,28 @@ class Xcode {
return _isSimctlInstalled ?? false; return _isSimctlInstalled ?? false;
} }
bool? _isDevicectlInstalled;
/// Verifies that `devicectl` is installed by checking Xcode version and trying
/// to run it. `devicectl` is made available in Xcode 15.
bool get isDevicectlInstalled {
if (_isDevicectlInstalled == null) {
try {
if (currentVersion == null || currentVersion!.major < 15) {
_isDevicectlInstalled = false;
return _isDevicectlInstalled!;
}
final RunResult result = _processUtils.runSync(
<String>[...xcrunCommand(), 'devicectl', '--version'],
);
_isDevicectlInstalled = result.exitCode == 0;
} on ProcessException {
_isDevicectlInstalled = false;
}
}
return _isDevicectlInstalled ?? false;
}
bool get isRequiredVersionSatisfactory { bool get isRequiredVersionSatisfactory {
final Version? version = currentVersion; final Version? version = currentVersion;
if (version == null) { if (version == null) {
......
...@@ -130,9 +130,9 @@ class MDnsVmServiceDiscovery { ...@@ -130,9 +130,9 @@ class MDnsVmServiceDiscovery {
/// The [deviceVmservicePort] parameter must be set to specify which port /// The [deviceVmservicePort] parameter must be set to specify which port
/// to find. /// to find.
/// ///
/// [applicationId] and [deviceVmservicePort] are required for launch so that /// [applicationId] and either [deviceVmservicePort] or [deviceName] are
/// if multiple flutter apps are running on different devices, it will /// required for launch so that if multiple flutter apps are running on
/// only match with the device running the desired app. /// different devices, it will only match with the device running the desired app.
/// ///
/// The [useDeviceIPAsHost] parameter flags whether to get the device IP /// The [useDeviceIPAsHost] parameter flags whether to get the device IP
/// and the [ipv6] parameter flags whether to get an iPv6 address /// and the [ipv6] parameter flags whether to get an iPv6 address
...@@ -141,21 +141,27 @@ class MDnsVmServiceDiscovery { ...@@ -141,21 +141,27 @@ class MDnsVmServiceDiscovery {
/// The [timeout] parameter determines how long to continue to wait for /// The [timeout] parameter determines how long to continue to wait for
/// services to become active. /// services to become active.
/// ///
/// If a Dart VM Service matching the [applicationId] and [deviceVmservicePort] /// If a Dart VM Service matching the [applicationId] and
/// cannot be found after the [timeout], it will call [throwToolExit]. /// [deviceVmservicePort]/[deviceName] cannot be found before the [timeout]
/// is reached, it will call [throwToolExit].
@visibleForTesting @visibleForTesting
Future<MDnsVmServiceDiscoveryResult?> queryForLaunch({ Future<MDnsVmServiceDiscoveryResult?> queryForLaunch({
required String applicationId, required String applicationId,
required int deviceVmservicePort, int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false, bool ipv6 = false,
bool useDeviceIPAsHost = false, bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10), Duration timeout = const Duration(minutes: 10),
}) async { }) async {
// Query for a specific application and device port. // Either the device port or the device name must be provided.
assert(deviceVmservicePort != null || deviceName != null);
// Query for a specific application matching on either device port or device name.
return firstMatchingVmService( return firstMatchingVmService(
_client, _client,
applicationId: applicationId, applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort, deviceVmservicePort: deviceVmservicePort,
deviceName: deviceName,
ipv6: ipv6, ipv6: ipv6,
useDeviceIPAsHost: useDeviceIPAsHost, useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout, timeout: timeout,
...@@ -170,6 +176,7 @@ class MDnsVmServiceDiscovery { ...@@ -170,6 +176,7 @@ class MDnsVmServiceDiscovery {
MDnsClient client, { MDnsClient client, {
String? applicationId, String? applicationId,
int? deviceVmservicePort, int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false, bool ipv6 = false,
bool useDeviceIPAsHost = false, bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10), Duration timeout = const Duration(minutes: 10),
...@@ -178,6 +185,7 @@ class MDnsVmServiceDiscovery { ...@@ -178,6 +185,7 @@ class MDnsVmServiceDiscovery {
client, client,
applicationId: applicationId, applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort, deviceVmservicePort: deviceVmservicePort,
deviceName: deviceName,
ipv6: ipv6, ipv6: ipv6,
useDeviceIPAsHost: useDeviceIPAsHost, useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout, timeout: timeout,
...@@ -193,6 +201,7 @@ class MDnsVmServiceDiscovery { ...@@ -193,6 +201,7 @@ class MDnsVmServiceDiscovery {
MDnsClient client, { MDnsClient client, {
String? applicationId, String? applicationId,
int? deviceVmservicePort, int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false, bool ipv6 = false,
bool useDeviceIPAsHost = false, bool useDeviceIPAsHost = false,
required Duration timeout, required Duration timeout,
...@@ -263,6 +272,11 @@ class MDnsVmServiceDiscovery { ...@@ -263,6 +272,11 @@ class MDnsVmServiceDiscovery {
continue; continue;
} }
// If deviceName is set, only use records that match it
if (deviceName != null && !deviceNameMatchesTargetName(deviceName, srvRecord.target)) {
continue;
}
// Get the IP address of the device if using the IP as the host. // Get the IP address of the device if using the IP as the host.
InternetAddress? ipAddress; InternetAddress? ipAddress;
if (useDeviceIPAsHost) { if (useDeviceIPAsHost) {
...@@ -332,6 +346,15 @@ class MDnsVmServiceDiscovery { ...@@ -332,6 +346,15 @@ class MDnsVmServiceDiscovery {
} }
} }
@visibleForTesting
bool deviceNameMatchesTargetName(String deviceName, String targetName) {
// Remove `.local` from the name along with any non-word, non-digit characters.
final RegExp cleanedNameRegex = RegExp(r'\.local|\W');
final String cleanedDeviceName = deviceName.trim().toLowerCase().replaceAll(cleanedNameRegex, '');
final String cleanedTargetName = targetName.toLowerCase().replaceAll(cleanedNameRegex, '');
return cleanedDeviceName == cleanedTargetName;
}
String _getAuthCode(String txtRecord) { String _getAuthCode(String txtRecord) {
const String authCodePrefix = 'authCode='; const String authCodePrefix = 'authCode=';
final Iterable<String> matchingRecords = final Iterable<String> matchingRecords =
...@@ -354,7 +377,7 @@ class MDnsVmServiceDiscovery { ...@@ -354,7 +377,7 @@ class MDnsVmServiceDiscovery {
/// When [useDeviceIPAsHost] is true, it will use the device's IP as the /// When [useDeviceIPAsHost] is true, it will use the device's IP as the
/// host and will not forward the port. /// host and will not forward the port.
/// ///
/// Differs from `getVMServiceUriForLaunch` because it can search for any available Dart VM Service. /// Differs from [getVMServiceUriForLaunch] because it can search for any available Dart VM Service.
/// Since [applicationId] and [deviceVmservicePort] are optional, it can either look for any service /// Since [applicationId] and [deviceVmservicePort] are optional, it can either look for any service
/// or a specific service matching [applicationId]/[deviceVmservicePort]. /// or a specific service matching [applicationId]/[deviceVmservicePort].
/// It may find more than one service, which will throw an error listing the found services. /// It may find more than one service, which will throw an error listing the found services.
...@@ -391,20 +414,22 @@ class MDnsVmServiceDiscovery { ...@@ -391,20 +414,22 @@ class MDnsVmServiceDiscovery {
/// When [useDeviceIPAsHost] is true, it will use the device's IP as the /// When [useDeviceIPAsHost] is true, it will use the device's IP as the
/// host and will not forward the port. /// host and will not forward the port.
/// ///
/// Differs from `getVMServiceUriForAttach` because it only searches for a specific service. /// Differs from [getVMServiceUriForAttach] because it only searches for a specific service.
/// This is enforced by [applicationId] and [deviceVmservicePort] being required. /// This is enforced by [applicationId] being required and using either the
/// [deviceVmservicePort] or the [device]'s name to query.
Future<Uri?> getVMServiceUriForLaunch( Future<Uri?> getVMServiceUriForLaunch(
String applicationId, String applicationId,
Device device, { Device device, {
bool usesIpv6 = false, bool usesIpv6 = false,
int? hostVmservicePort, int? hostVmservicePort,
required int deviceVmservicePort, int? deviceVmservicePort,
bool useDeviceIPAsHost = false, bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10), Duration timeout = const Duration(minutes: 10),
}) async { }) async {
final MDnsVmServiceDiscoveryResult? result = await queryForLaunch( final MDnsVmServiceDiscoveryResult? result = await queryForLaunch(
applicationId: applicationId, applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort, deviceVmservicePort: deviceVmservicePort,
deviceName: deviceVmservicePort == null ? device.name : null,
ipv6: usesIpv6, ipv6: usesIpv6,
useDeviceIPAsHost: useDeviceIPAsHost, useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout, timeout: timeout,
......
...@@ -339,6 +339,15 @@ ...@@ -339,6 +339,15 @@
"templates/skeleton/README.md.tmpl", "templates/skeleton/README.md.tmpl",
"templates/skeleton/test/implementation_test.dart.test.tmpl", "templates/skeleton/test/implementation_test.dart.test.tmpl",
"templates/skeleton/test/unit_test.dart.tmpl", "templates/skeleton/test/unit_test.dart.tmpl",
"templates/skeleton/test/widget_test.dart.tmpl" "templates/skeleton/test/widget_test.dart.tmpl",
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata",
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist",
"templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings",
"templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.pbxproj",
"templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata",
"templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme.tmpl"
] ]
} }
# Template Xcode project with a custom application bundle
This template is an empty Xcode project with a settable application bundle path
within the `xcscheme`. It is used when debugging a project on a physical iOS 17+
device via Xcode 15+ when `--use-application-binary` is used.
\ No newline at end of file
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXFileReference section */
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<PathRunnable
runnableDebuggingMode = "0"
FilePath = "{{applicationBundlePath}}">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<PathRunnable
runnableDebuggingMode = "0"
FilePath = "{{applicationBundlePath}}">
</PathRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
...@@ -884,6 +884,26 @@ void main() { ...@@ -884,6 +884,26 @@ void main() {
); );
}); });
testWithoutContext('Get launch arguments for physical CoreDevice with debugging enabled with no launch arguments', () {
final DebuggingOptions original = DebuggingOptions.enabled(
BuildInfo.debug,
);
final List<String> launchArguments = original.getIOSLaunchArguments(
EnvironmentType.physical,
null,
<String, Object?>{},
isCoreDevice: true,
);
expect(
launchArguments.join(' '),
<String>[
'--enable-dart-profiling',
].join(' '),
);
});
testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () { testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () {
final DebuggingOptions original = DebuggingOptions.enabled( final DebuggingOptions original = DebuggingOptions.enabled(
BuildInfo.debug, BuildInfo.debug,
......
...@@ -12,10 +12,13 @@ import 'package:flutter_tools/src/build_info.dart'; ...@@ -12,10 +12,13 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/application_package.dart'; import 'package:flutter_tools/src/ios/application_package.dart';
import 'package:flutter_tools/src/ios/core_devices.dart';
import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcode_debug.dart';
import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/fake_process_manager.dart'; import '../../src/fake_process_manager.dart';
...@@ -105,6 +108,28 @@ void main() { ...@@ -105,6 +108,28 @@ void main() {
expect(processManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('IOSDevice.installApp uses devicectl for CoreDevices', () async {
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
uncompressedBundle: fileSystem.currentDirectory,
applicationPackage: bundleDirectory,
);
final FakeProcessManager processManager = FakeProcessManager.empty();
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
interfaceType: DeviceConnectionInterface.attached,
artifacts: artifacts,
isCoreDevice: true,
);
final bool wasInstalled = await device.installApp(iosApp);
expect(wasInstalled, true);
expect(processManager, hasNoRemainingExpectations);
});
testWithoutContext('IOSDevice.uninstallApp calls ios-deploy correctly', () async { testWithoutContext('IOSDevice.uninstallApp calls ios-deploy correctly', () async {
final IOSApp iosApp = PrebuiltIOSApp( final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app', projectBundleId: 'app',
...@@ -134,6 +159,28 @@ void main() { ...@@ -134,6 +159,28 @@ void main() {
expect(processManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
}); });
testWithoutContext('IOSDevice.uninstallApp uses devicectl for CoreDevices', () async {
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
uncompressedBundle: fileSystem.currentDirectory,
applicationPackage: bundleDirectory,
);
final FakeProcessManager processManager = FakeProcessManager.empty();
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
interfaceType: DeviceConnectionInterface.attached,
artifacts: artifacts,
isCoreDevice: true,
);
final bool wasUninstalled = await device.uninstallApp(iosApp);
expect(wasUninstalled, true);
expect(processManager, hasNoRemainingExpectations);
});
group('isAppInstalled', () { group('isAppInstalled', () {
testWithoutContext('catches ProcessException from ios-deploy', () async { testWithoutContext('catches ProcessException from ios-deploy', () async {
final IOSApp iosApp = PrebuiltIOSApp( final IOSApp iosApp = PrebuiltIOSApp(
...@@ -263,6 +310,28 @@ void main() { ...@@ -263,6 +310,28 @@ void main() {
expect(processManager, hasNoRemainingExpectations); expect(processManager, hasNoRemainingExpectations);
expect(logger.traceText, contains(stderr)); expect(logger.traceText, contains(stderr));
}); });
testWithoutContext('uses devicectl for CoreDevices', () async {
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
uncompressedBundle: fileSystem.currentDirectory,
applicationPackage: bundleDirectory,
);
final FakeProcessManager processManager = FakeProcessManager.empty();
final IOSDevice device = setUpIOSDevice(
processManager: processManager,
fileSystem: fileSystem,
interfaceType: DeviceConnectionInterface.attached,
artifacts: artifacts,
isCoreDevice: true,
);
final bool wasInstalled = await device.isAppInstalled(iosApp);
expect(wasInstalled, true);
expect(processManager, hasNoRemainingExpectations);
});
}); });
testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async { testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async {
...@@ -314,6 +383,8 @@ void main() { ...@@ -314,6 +383,8 @@ void main() {
expect(wasAppUninstalled, false); expect(wasAppUninstalled, false);
}); });
} }
IOSDevice setUpIOSDevice({ IOSDevice setUpIOSDevice({
...@@ -322,6 +393,7 @@ IOSDevice setUpIOSDevice({ ...@@ -322,6 +393,7 @@ IOSDevice setUpIOSDevice({
Logger? logger, Logger? logger,
DeviceConnectionInterface? interfaceType, DeviceConnectionInterface? interfaceType,
Artifacts? artifacts, Artifacts? artifacts,
bool isCoreDevice = false,
}) { }) {
logger ??= BufferLogger.test(); logger ??= BufferLogger.test();
final FakePlatform platform = FakePlatform( final FakePlatform platform = FakePlatform(
...@@ -357,9 +429,42 @@ IOSDevice setUpIOSDevice({ ...@@ -357,9 +429,42 @@ IOSDevice setUpIOSDevice({
artifacts: artifacts, artifacts: artifacts,
cache: cache, cache: cache,
), ),
coreDeviceControl: FakeIOSCoreDeviceControl(),
xcodeDebug: FakeXcodeDebug(),
iProxy: IProxy.test(logger: logger, processManager: processManager), iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached, connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true, devModeEnabled: true,
isCoreDevice: isCoreDevice,
); );
} }
class FakeXcodeDebug extends Fake implements XcodeDebug {}
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {
@override
Future<bool> installApp({
required String deviceId,
required String bundlePath,
}) async {
return true;
}
@override
Future<bool> uninstallApp({
required String deviceId,
required String bundleId,
}) async {
return true;
}
@override
Future<bool> isAppInstalled({
required String deviceId,
required String bundleId,
}) async {
return true;
}
}
...@@ -10,11 +10,14 @@ import 'package:flutter_tools/src/base/platform.dart'; ...@@ -10,11 +10,14 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/core_devices.dart';
import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/devices.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcode_debug.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -94,6 +97,8 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { ...@@ -94,6 +97,8 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
cache: Cache.test(processManager: processManager), cache: Cache.test(processManager: processManager),
), ),
iMobileDevice: IMobileDevice.test(processManager: processManager), iMobileDevice: IMobileDevice.test(processManager: processManager),
coreDeviceControl: FakeIOSCoreDeviceControl(),
xcodeDebug: FakeXcodeDebug(),
platform: platform, platform: platform,
name: 'iPhone 1', name: 'iPhone 1',
sdkVersion: '13.3', sdkVersion: '13.3',
...@@ -102,5 +107,10 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { ...@@ -102,5 +107,10 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
connectionInterface: DeviceConnectionInterface.attached, connectionInterface: DeviceConnectionInterface.attached,
isConnected: true, isConnected: true,
devModeEnabled: true, devModeEnabled: true,
isCoreDevice: false,
); );
} }
class FakeXcodeDebug extends Fake implements XcodeDebug {}
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment