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:
["devicelab", "ios", "mac"]
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
recipe: devicelab/devicelab_drone
presubmit: false
......@@ -3752,6 +3762,16 @@ targets:
["devicelab", "ios", "mac"]
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
recipe: devicelab/devicelab_drone
presubmit: false
......
......@@ -168,6 +168,7 @@
/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__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_view_ios__start_up.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine
......@@ -178,6 +179,7 @@
/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_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_keyboard_resize.dart @cyanglaz @flutter/engine
/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() {
).call;
}
TaskFunction createEndToEndDriverTest() {
TaskFunction createEndToEndDriverTest({Map<String, String>? environment}) {
return DriverTest(
'${flutterDirectory.path}/dev/integration_tests/ui',
'lib/driver.dart',
environment: environment,
).call;
}
......@@ -173,6 +174,7 @@ class DriverTest {
this.testTarget, {
this.extraOptions = const <String>[],
this.deviceIdOverride,
this.environment,
}
);
......@@ -180,6 +182,7 @@ class DriverTest {
final String testTarget;
final List<String> extraOptions;
final String? deviceIdOverride;
final Map<String, String>? environment;
Future<TaskResult> call() {
return inDirectory<TaskResult>(testDirectory, () async {
......@@ -202,7 +205,7 @@ class DriverTest {
deviceId,
...extraOptions,
];
await flutter('drive', options: options);
await flutter('drive', options: options, environment: environment);
return TaskResult.success(null);
});
......
......@@ -233,10 +233,11 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) {
).run;
}
TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart'}) {
TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart', Map<String, String>? runEnvironment}) {
return StartupTest(
'${flutterDirectory.path}/dev/integration_tests/flutter_gallery',
target: target,
runEnvironment: runEnvironment,
).run;
}
......@@ -768,11 +769,17 @@ Future<void> _resetManifest(String testDirectory) async {
/// Measure application startup performance.
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 bool reportMetrics;
final String target;
final Map<String, String>? runEnvironment;
Future<TaskResult> run() async {
return inDirectory<TaskResult>(testDirectory, () async {
......@@ -855,7 +862,9 @@ class StartupTest {
'screenshot_startup_${DateTime.now().toLocal().toIso8601String()}.png',
);
});
final int result = await flutter('run', options: <String>[
final int result = await flutter(
'run',
options: <String>[
'--no-android-gradle-daemon',
'--no-publish-port',
'--verbose',
......@@ -869,7 +878,10 @@ class StartupTest {
device.deviceId,
if (applicationBinaryPath != null)
'--use-application-binary=$applicationBinaryPath',
], canFail: true);
],
environment: runEnvironment,
canFail: true,
);
timer.cancel();
if (result == 0) {
final Map<String, dynamic> data = json.decode(
......
This diff is collapsed.
......@@ -359,6 +359,7 @@ Future<T> runInContext<T>(
platform: globals.platform,
fileSystem: globals.fs,
xcodeProjectInterpreter: globals.xcodeProjectInterpreter!,
userMessages: globals.userMessages,
),
XCDevice: () => XCDevice(
processManager: globals.processManager,
......@@ -375,6 +376,7 @@ Future<T> runInContext<T>(
processManager: globals.processManager,
dyLdLibEntry: globals.cache.dyLdLibEntry,
),
fileSystem: globals.fs,
),
XcodeProjectInterpreter: () => XcodeProjectInterpreter(
logger: globals.logger,
......
......@@ -1159,6 +1159,7 @@ class DebuggingOptions {
Map<String, Object?> platformArgs, {
bool ipv6 = false,
DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached,
bool isCoreDevice = false,
}) {
final String dartVmFlags = computeDartVmFlags(this);
return <String>[
......@@ -1172,7 +1173,10 @@ class DebuggingOptions {
if (environmentType == EnvironmentType.simulator && dartVmFlags.isNotEmpty)
'--dart-flags=$dartVmFlags',
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',
'--verify-entry-points',
],
......
This diff is collapsed.
......@@ -132,6 +132,7 @@ Future<XcodeBuildResult> buildXcodeProject({
DarwinArch? activeArch,
bool codesign = true,
String? deviceID,
bool isCoreDevice = false,
bool configOnly = false,
XcodeBuildAction buildAction = XcodeBuildAction.build,
}) async {
......@@ -240,6 +241,7 @@ Future<XcodeBuildResult> buildXcodeProject({
project: project,
targetOverride: targetOverride,
buildInfo: buildInfo,
usingCoreDevice: isCoreDevice,
);
await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode);
if (configOnly) {
......
......@@ -35,6 +35,7 @@ Future<void> updateGeneratedXcodeProperties({
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
bool usingCoreDevice = false,
}) async {
final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines(
project: project,
......@@ -42,6 +43,7 @@ Future<void> updateGeneratedXcodeProperties({
targetOverride: targetOverride,
useMacOSConfig: useMacOSConfig,
buildDirOverride: buildDirOverride,
usingCoreDevice: usingCoreDevice,
);
_updateGeneratedXcodePropertiesFile(
......@@ -143,6 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({
String? targetOverride,
bool useMacOSConfig = false,
String? buildDirOverride,
bool usingCoreDevice = false,
}) async {
final List<String> xcodeBuildSettings = <String>[];
......@@ -170,6 +173,12 @@ Future<List<String>> _xcodeBuildSettingsLines({
final String buildNumber = parsedBuildNumber(manifest: project.manifest, buildInfo: buildInfo) ?? '1';
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;
if (localEngineInfo != null) {
final String engineOutPath = localEngineInfo.engineOutPath;
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/platform.dart';
......@@ -18,10 +19,12 @@ import '../cache.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
import '../ios/core_devices.dart';
import '../ios/devices.dart';
import '../ios/ios_deploy.dart';
import '../ios/iproxy.dart';
import '../ios/mac.dart';
import '../ios/xcode_debug.dart';
import '../reporting/reporting.dart';
import 'xcode.dart';
......@@ -65,6 +68,10 @@ class XCDevice {
required Xcode xcode,
required Platform platform,
required IProxy iproxy,
required FileSystem fileSystem,
@visibleForTesting
IOSCoreDeviceControl? coreDeviceControl,
XcodeDebug? xcodeDebug,
}) : _processUtils = ProcessUtils(logger: logger, processManager: processManager),
_logger = logger,
_iMobileDevice = IMobileDevice(
......@@ -80,6 +87,18 @@ class XCDevice {
platform: platform,
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,
_xcode = xcode {
......@@ -99,6 +118,8 @@ class XCDevice {
final IOSDeploy _iosDeploy;
final Xcode _xcode;
final IProxy _iProxy;
final IOSCoreDeviceControl _coreDeviceControl;
final XcodeDebug _xcodeDebug;
List<Object>? _cachedListResults;
......@@ -457,6 +478,17 @@ class XCDevice {
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,
......@@ -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(
identifier,
name: name,
cpuArchitecture: _cpuArchitecture(device),
connectionInterface: _interfaceType(device),
connectionInterface: connectionInterface,
isConnected: isConnected,
sdkVersion: sdkVersionString,
iProxy: _iProxy,
......@@ -577,8 +625,11 @@ class XCDevice {
logger: _logger,
iosDeploy: _iosDeploy,
iMobileDevice: _iMobileDevice,
coreDeviceControl: _coreDeviceControl,
xcodeDebug: _xcodeDebug,
platform: globals.platform,
devModeEnabled: devModeEnabled,
isCoreDevice: coreDevice != null,
);
}
}
......
......@@ -14,8 +14,10 @@ import '../base/io.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/user_messages.dart';
import '../base/version.dart';
import '../build_info.dart';
import '../cache.dart';
import '../ios/xcodeproj.dart';
Version get xcodeRequiredVersion => Version(14, null, null);
......@@ -44,9 +46,13 @@ class Xcode {
required Logger logger,
required FileSystem fileSystem,
required XcodeProjectInterpreter xcodeProjectInterpreter,
required UserMessages userMessages,
String? flutterRoot,
}) : _platform = platform,
_fileSystem = fileSystem,
_xcodeProjectInterpreter = xcodeProjectInterpreter,
_userMessage = userMessages,
_flutterRoot = flutterRoot,
_processUtils =
ProcessUtils(logger: logger, processManager: processManager),
_logger = logger;
......@@ -61,6 +67,7 @@ class Xcode {
XcodeProjectInterpreter? xcodeProjectInterpreter,
Platform? platform,
FileSystem? fileSystem,
String? flutterRoot,
Logger? logger,
}) {
platform ??= FakePlatform(
......@@ -72,6 +79,8 @@ class Xcode {
platform: platform,
processManager: processManager,
fileSystem: fileSystem ?? MemoryFileSystem.test(),
userMessages: UserMessages(),
flutterRoot: flutterRoot,
logger: logger,
xcodeProjectInterpreter: xcodeProjectInterpreter ?? XcodeProjectInterpreter.test(processManager: processManager),
);
......@@ -81,6 +90,8 @@ class Xcode {
final ProcessUtils _processUtils;
final FileSystem _fileSystem;
final XcodeProjectInterpreter _xcodeProjectInterpreter;
final UserMessages _userMessage;
final String? _flutterRoot;
final Logger _logger;
bool get isInstalledAndMeetsVersionCheck => _platform.isMacOS && isInstalled && isRequiredVersionSatisfactory;
......@@ -101,6 +112,38 @@ class Xcode {
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;
Version? get currentVersion => _xcodeProjectInterpreter.version;
......@@ -150,6 +193,28 @@ class Xcode {
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 {
final Version? version = currentVersion;
if (version == null) {
......
......@@ -130,9 +130,9 @@ class MDnsVmServiceDiscovery {
/// The [deviceVmservicePort] parameter must be set to specify which port
/// to find.
///
/// [applicationId] and [deviceVmservicePort] are required for launch so that
/// if multiple flutter apps are running on different devices, it will
/// only match with the device running the desired app.
/// [applicationId] and either [deviceVmservicePort] or [deviceName] are
/// required for launch so that if multiple flutter apps are running on
/// different devices, it will only match with the device running the desired app.
///
/// The [useDeviceIPAsHost] parameter flags whether to get the device IP
/// and the [ipv6] parameter flags whether to get an iPv6 address
......@@ -141,21 +141,27 @@ class MDnsVmServiceDiscovery {
/// The [timeout] parameter determines how long to continue to wait for
/// services to become active.
///
/// If a Dart VM Service matching the [applicationId] and [deviceVmservicePort]
/// cannot be found after the [timeout], it will call [throwToolExit].
/// If a Dart VM Service matching the [applicationId] and
/// [deviceVmservicePort]/[deviceName] cannot be found before the [timeout]
/// is reached, it will call [throwToolExit].
@visibleForTesting
Future<MDnsVmServiceDiscoveryResult?> queryForLaunch({
required String applicationId,
required int deviceVmservicePort,
int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false,
bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10),
}) 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(
_client,
applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort,
deviceName: deviceName,
ipv6: ipv6,
useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout,
......@@ -170,6 +176,7 @@ class MDnsVmServiceDiscovery {
MDnsClient client, {
String? applicationId,
int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false,
bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10),
......@@ -178,6 +185,7 @@ class MDnsVmServiceDiscovery {
client,
applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort,
deviceName: deviceName,
ipv6: ipv6,
useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout,
......@@ -193,6 +201,7 @@ class MDnsVmServiceDiscovery {
MDnsClient client, {
String? applicationId,
int? deviceVmservicePort,
String? deviceName,
bool ipv6 = false,
bool useDeviceIPAsHost = false,
required Duration timeout,
......@@ -263,6 +272,11 @@ class MDnsVmServiceDiscovery {
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.
InternetAddress? ipAddress;
if (useDeviceIPAsHost) {
......@@ -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) {
const String authCodePrefix = 'authCode=';
final Iterable<String> matchingRecords =
......@@ -354,7 +377,7 @@ class MDnsVmServiceDiscovery {
/// When [useDeviceIPAsHost] is true, it will use the device's IP as the
/// 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
/// or a specific service matching [applicationId]/[deviceVmservicePort].
/// It may find more than one service, which will throw an error listing the found services.
......@@ -391,20 +414,22 @@ class MDnsVmServiceDiscovery {
/// When [useDeviceIPAsHost] is true, it will use the device's IP as the
/// host and will not forward the port.
///
/// Differs from `getVMServiceUriForAttach` because it only searches for a specific service.
/// This is enforced by [applicationId] and [deviceVmservicePort] being required.
/// Differs from [getVMServiceUriForAttach] because it only searches for a specific service.
/// This is enforced by [applicationId] being required and using either the
/// [deviceVmservicePort] or the [device]'s name to query.
Future<Uri?> getVMServiceUriForLaunch(
String applicationId,
Device device, {
bool usesIpv6 = false,
int? hostVmservicePort,
required int deviceVmservicePort,
int? deviceVmservicePort,
bool useDeviceIPAsHost = false,
Duration timeout = const Duration(minutes: 10),
}) async {
final MDnsVmServiceDiscoveryResult? result = await queryForLaunch(
applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort,
deviceName: deviceVmservicePort == null ? device.name : null,
ipv6: usesIpv6,
useDeviceIPAsHost: useDeviceIPAsHost,
timeout: timeout,
......
......@@ -339,6 +339,15 @@
"templates/skeleton/README.md.tmpl",
"templates/skeleton/test/implementation_test.dart.test.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() {
);
});
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', () {
final DebuggingOptions original = DebuggingOptions.enabled(
BuildInfo.debug,
......
......@@ -12,10 +12,13 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/device.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/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.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/fake_process_manager.dart';
......@@ -105,6 +108,28 @@ void main() {
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 {
final IOSApp iosApp = PrebuiltIOSApp(
projectBundleId: 'app',
......@@ -134,6 +159,28 @@ void main() {
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', () {
testWithoutContext('catches ProcessException from ios-deploy', () async {
final IOSApp iosApp = PrebuiltIOSApp(
......@@ -263,6 +310,28 @@ void main() {
expect(processManager, hasNoRemainingExpectations);
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 {
......@@ -314,6 +383,8 @@ void main() {
expect(wasAppUninstalled, false);
});
}
IOSDevice setUpIOSDevice({
......@@ -322,6 +393,7 @@ IOSDevice setUpIOSDevice({
Logger? logger,
DeviceConnectionInterface? interfaceType,
Artifacts? artifacts,
bool isCoreDevice = false,
}) {
logger ??= BufferLogger.test();
final FakePlatform platform = FakePlatform(
......@@ -357,9 +429,42 @@ IOSDevice setUpIOSDevice({
artifacts: artifacts,
cache: cache,
),
coreDeviceControl: FakeIOSCoreDeviceControl(),
xcodeDebug: FakeXcodeDebug(),
iProxy: IProxy.test(logger: logger, processManager: processManager),
connectionInterface: interfaceType ?? DeviceConnectionInterface.attached,
isConnected: 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';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.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/ios_deploy.dart';
import 'package:flutter_tools/src/ios/iproxy.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:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -94,6 +97,8 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
cache: Cache.test(processManager: processManager),
),
iMobileDevice: IMobileDevice.test(processManager: processManager),
coreDeviceControl: FakeIOSCoreDeviceControl(),
xcodeDebug: FakeXcodeDebug(),
platform: platform,
name: 'iPhone 1',
sdkVersion: '13.3',
......@@ -102,5 +107,10 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) {
connectionInterface: DeviceConnectionInterface.attached,
isConnected: true,
devModeEnabled: true,
isCoreDevice: false,
);
}
class FakeXcodeDebug extends Fake implements XcodeDebug {}
class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {}
......@@ -1306,5 +1306,75 @@ flutter:
expectedBuildNumber: '1',
);
});
group('CoreDevice', () {
testUsingContext('sets BUILD_DIR for core devices in debug mode', () async {
const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
useMacOSConfig: true,
usingCoreDevice: true,
);
final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents, contains('\nBUILD_DIR=/build/ios\n'));
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
Platform: () => macOS,
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
testUsingContext('does not set BUILD_DIR for core devices in release mode', () async {
const BuildInfo buildInfo = BuildInfo.release;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
useMacOSConfig: true,
usingCoreDevice: true,
);
final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('\nBUILD_DIR'), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
Platform: () => macOS,
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
testUsingContext('does not set BUILD_DIR for non core devices', () async {
const BuildInfo buildInfo = BuildInfo.debug;
final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project'));
await updateGeneratedXcodeProperties(
project: project,
buildInfo: buildInfo,
useMacOSConfig: true,
);
final File config = fs.file('path/to/project/macos/Flutter/ephemeral/Flutter-Generated.xcconfig');
expect(config.existsSync(), isTrue);
final String contents = config.readAsStringSync();
expect(contents.contains('\nBUILD_DIR'), isFalse);
}, overrides: <Type, Generator>{
Artifacts: () => localIosArtifacts,
Platform: () => macOS,
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
});
});
}
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