| // 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 'dart:async'; |
| |
| import 'package:meta/meta.dart'; |
| import 'package:process/process.dart'; |
| |
| import '../artifacts.dart'; |
| import '../base/logger.dart'; |
| import '../base/platform.dart'; |
| import '../base/process.dart'; |
| import '../build_info.dart'; |
| import '../cache.dart'; |
| import 'code_signing.dart'; |
| import 'devices.dart'; |
| |
| // Error message patterns from ios-deploy output |
| const String noProvisioningProfileErrorOne = 'Error 0xe8008015'; |
| const String noProvisioningProfileErrorTwo = 'Error 0xe8000067'; |
| const String deviceLockedError = 'e80000e2'; |
| const String unknownAppLaunchError = 'Error 0xe8000022'; |
| |
| class IOSDeploy { |
| IOSDeploy({ |
| @required Artifacts artifacts, |
| @required Cache cache, |
| @required Logger logger, |
| @required Platform platform, |
| @required ProcessManager processManager, |
| }) : _platform = platform, |
| _cache = cache, |
| _processUtils = ProcessUtils(processManager: processManager, logger: logger), |
| _logger = logger, |
| _binaryPath = artifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios); |
| |
| final Cache _cache; |
| final String _binaryPath; |
| final Logger _logger; |
| final Platform _platform; |
| final ProcessUtils _processUtils; |
| |
| Map<String, String> get iosDeployEnv { |
| // Push /usr/bin to the front of PATH to pick up default system python, package 'six'. |
| // |
| // ios-deploy transitively depends on LLDB.framework, which invokes a |
| // Python script that uses package 'six'. LLDB.framework relies on the |
| // python at the front of the path, which may not include package 'six'. |
| // Ensure that we pick up the system install of python, which includes it. |
| final Map<String, String> environment = Map<String, String>.of(_platform.environment); |
| environment['PATH'] = '/usr/bin:${environment['PATH']}'; |
| environment.addEntries(<MapEntry<String, String>>[_cache.dyLdLibEntry]); |
| return environment; |
| } |
| |
| /// Uninstalls the specified app bundle. |
| /// |
| /// Uses ios-deploy and returns the exit code. |
| Future<int> uninstallApp({ |
| @required String deviceId, |
| @required String bundleId, |
| }) async { |
| final List<String> launchCommand = <String>[ |
| _binaryPath, |
| '--id', |
| deviceId, |
| '--uninstall_only', |
| '--bundle_id', |
| bundleId, |
| ]; |
| |
| return _processUtils.stream( |
| launchCommand, |
| mapFunction: _monitorFailure, |
| trace: true, |
| environment: iosDeployEnv, |
| ); |
| } |
| |
| /// Installs the specified app bundle. |
| /// |
| /// Uses ios-deploy and returns the exit code. |
| Future<int> installApp({ |
| @required String deviceId, |
| @required String bundlePath, |
| @required List<String>launchArguments, |
| @required IOSDeviceInterface interfaceType, |
| }) async { |
| final List<String> launchCommand = <String>[ |
| _binaryPath, |
| '--id', |
| deviceId, |
| '--bundle', |
| bundlePath, |
| if (interfaceType != IOSDeviceInterface.network) |
| '--no-wifi', |
| if (launchArguments.isNotEmpty) ...<String>[ |
| '--args', |
| launchArguments.join(' '), |
| ], |
| ]; |
| |
| return _processUtils.stream( |
| launchCommand, |
| mapFunction: _monitorFailure, |
| trace: true, |
| environment: iosDeployEnv, |
| ); |
| } |
| |
| /// Installs and then runs the specified app bundle. |
| /// |
| /// Uses ios-deploy and returns the exit code. |
| Future<int> runApp({ |
| @required String deviceId, |
| @required String bundlePath, |
| @required List<String> launchArguments, |
| @required IOSDeviceInterface interfaceType, |
| }) async { |
| final List<String> launchCommand = <String>[ |
| _binaryPath, |
| '--id', |
| deviceId, |
| '--bundle', |
| bundlePath, |
| if (interfaceType != IOSDeviceInterface.network) |
| '--no-wifi', |
| '--justlaunch', |
| if (launchArguments.isNotEmpty) ...<String>[ |
| '--args', |
| launchArguments.join(' '), |
| ], |
| ]; |
| |
| return _processUtils.stream( |
| launchCommand, |
| mapFunction: _monitorFailure, |
| trace: true, |
| environment: iosDeployEnv, |
| ); |
| } |
| |
| Future<bool> isAppInstalled({ |
| @required String bundleId, |
| @required String deviceId, |
| }) async { |
| final List<String> launchCommand = <String>[ |
| _binaryPath, |
| '--id', |
| deviceId, |
| '--exists', |
| '--timeout', // If the device is not connected, ios-deploy will wait forever. |
| '10', |
| '--bundle_id', |
| bundleId, |
| ]; |
| final RunResult result = await _processUtils.run( |
| launchCommand, |
| environment: iosDeployEnv, |
| ); |
| // Device successfully connected, but app not installed. |
| if (result.exitCode == 255) { |
| _logger.printTrace('$bundleId not installed on $deviceId'); |
| return false; |
| } |
| if (result.exitCode != 0) { |
| _logger.printTrace('App install check failed: ${result.stderr}'); |
| return false; |
| } |
| return true; |
| } |
| |
| // Maps stdout line stream. Must return original line. |
| String _monitorFailure(String stdout) { |
| // Installation issues. |
| if (stdout.contains(noProvisioningProfileErrorOne) || stdout.contains(noProvisioningProfileErrorTwo)) { |
| _logger.printError(noProvisioningProfileInstruction, emphasis: true); |
| |
| // Launch issues. |
| } else if (stdout.contains(deviceLockedError)) { |
| _logger.printError(''' |
| ═══════════════════════════════════════════════════════════════════════════════════ |
| Your device is locked. Unlock your device first before running. |
| ═══════════════════════════════════════════════════════════════════════════════════''', |
| emphasis: true); |
| } else if (stdout.contains(unknownAppLaunchError)) { |
| _logger.printError(''' |
| ═══════════════════════════════════════════════════════════════════════════════════ |
| Error launching app. Try launching from within Xcode via: |
| open ios/Runner.xcworkspace |
| |
| Your Xcode version may be too old for your iOS version. |
| ═══════════════════════════════════════════════════════════════════════════════════''', |
| emphasis: true); |
| } |
| |
| return stdout; |
| } |
| } |