Unverified Commit 704fb4cb authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Remove usage of ideviceinstaller in favor of ios-deploy (#50772)

parent 2078cc4d
......@@ -498,9 +498,7 @@ class IosDeviceDiscovery implements DeviceDiscovery {
'usbmuxd',
'libplist',
'openssl',
'ideviceinstaller',
'ios-deploy',
'libzip',
].map((String packageName) => path.join(getArtifactPath(), packageName)).join(':');
return <String, String>{'DYLD_LIBRARY_PATH': libPath};
}
......
......@@ -1261,9 +1261,7 @@ class IosUsbArtifacts extends CachedArtifact {
'usbmuxd',
'libplist',
'openssl',
'ideviceinstaller',
'ios-deploy',
'libzip',
];
// For unknown reasons, users are getting into bad states where libimobiledevice is
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import '../application_package.dart';
import '../base/common.dart';
import '../base/io.dart';
import '../cache.dart';
import '../device.dart';
import '../globals.dart' as globals;
......@@ -54,11 +55,15 @@ Future<bool> installApp(Device device, ApplicationPackage package, { bool uninst
return false;
}
if (uninstall && await device.isAppInstalled(package)) {
globals.printStatus('Uninstalling old version...');
if (!await device.uninstallApp(package)) {
globals.printError('Warning: uninstalling old version failed');
try {
if (uninstall && await device.isAppInstalled(package)) {
globals.printStatus('Uninstalling old version...');
if (!await device.uninstallApp(package)) {
globals.printError('Warning: uninstalling old version failed');
}
}
} on ProcessException catch (e) {
globals.printError('Error accessing device ${device.id}:\n${e.message}');
}
return device.installApp(package);
......
......@@ -35,7 +35,7 @@ import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
import 'globals.dart' as globals;
import 'ios/devices.dart' show IOSDeploy;
import 'ios/ios_deploy.dart';
import 'ios/ios_workflow.dart';
import 'ios/mac.dart';
import 'ios/simulators.dart';
......@@ -125,7 +125,13 @@ Future<T> runInContext<T>(
GradleUtils: () => GradleUtils(),
HotRunnerConfig: () => HotRunnerConfig(),
IMobileDevice: () => IMobileDevice(),
IOSDeploy: () => const IOSDeploy(),
IOSDeploy: () => IOSDeploy(
artifacts: globals.artifacts,
cache: globals.cache,
logger: globals.logger,
platform: globals.platform,
processManager: globals.processManager,
),
IOSSimulatorUtils: () => IOSSimulatorUtils(),
IOSWorkflow: () => const IOSWorkflow(),
KernelCompilerFactory: () => const KernelCompilerFactory(),
......
......@@ -20,6 +20,7 @@ import 'base/os.dart';
import 'base/terminal.dart';
import 'base/user_messages.dart';
import 'cache.dart';
import 'ios/ios_deploy.dart';
import 'ios/mac.dart';
import 'macos/xcode.dart';
import 'persistent_tool_state.dart';
......@@ -62,6 +63,7 @@ AndroidStudio get androidStudio => context.get<AndroidStudio>();
AndroidSdk get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>();
IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
IOSDeploy get iosDeploy => context.get<IOSDeploy>();
UserMessages get userMessages => context.get<UserMessages>();
Xcode get xcode => context.get<Xcode>();
......
// 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:platform/platform.dart';
import 'package:process/process.dart';
import '../artifacts.dart';
import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../cache.dart';
import 'code_signing.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>.from(_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,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--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,
}) async {
final List<String> launchCommand = <String>[
_binaryPath,
'--id',
deviceId,
'--bundle',
bundlePath,
'--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',
'--bundle_id',
bundleId,
];
final RunResult result = await _processUtils.run(
launchCommand,
environment: iosDeployEnv,
);
if (result.exitCode != 0) {
return false;
}
return result.stdout.contains(bundleId);
}
// 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;
}
}
......@@ -91,7 +91,6 @@ Future<XcodeBuildResult> buildXcodeProject({
return XcodeBuildResult(success: false);
}
final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path);
if (!projectInfo.targets.contains('Runner')) {
globals.printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
......
......@@ -15,6 +15,7 @@ import '../base/logger.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import '../ios/devices.dart';
import '../ios/xcodeproj.dart';
import '../reporting/reporting.dart';
......@@ -349,6 +350,10 @@ class XCDevice {
name: device['name'] as String,
cpuArchitecture: _cpuArchitecture(deviceProperties),
sdkVersion: _sdkVersion(deviceProperties),
artifacts: globals.artifacts,
fileSystem: globals.fs,
iosDeploy: globals.iosDeploy,
platform: globals.platform,
));
}
return devices;
......
// 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:io' show Process;
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/ios/ios_deploy.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/mocks.dart';
class MockArtifacts extends Mock implements Artifacts {}
class MockCache extends Mock implements Cache {}
class MockLogger extends Mock implements Logger {}
class MockPlatform extends Mock implements Platform {}
class MockProcess extends Mock implements Process {}
class MockProcessManager extends Mock implements ProcessManager {}
void main () {
group('IOSDeploy()', () {
Artifacts mockArtifacts;
Cache mockCache;
IOSDeploy iosDeploy;
Logger mockLogger;
Platform mockPlatform;
ProcessManager mockProcessManager;
const String iosDeployPath = '/path/to/ios-deploy';
const String deviceId = '123';
const String bundleId = 'com.example.app';
setUp(() {
mockArtifacts = MockArtifacts();
when(mockArtifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios))
.thenReturn(iosDeployPath);
mockCache = MockCache();
const MapEntry<String, String> mapEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', '/path/to/libs');
when(mockCache.dyLdLibEntry).thenReturn(mapEntry);
mockLogger = MockLogger();
mockPlatform = MockPlatform();
when(mockPlatform.environment).thenReturn(<String, String>{
'PATH': '/usr/local/bin:/usr/bin',
});
mockProcessManager = MockProcessManager();
iosDeploy = IOSDeploy(
artifacts: mockArtifacts,
cache: mockCache,
logger: mockLogger,
platform: mockPlatform,
processManager: mockProcessManager,
);
});
testWithoutContext('iosDeployEnv returns path with /usr/bin first', () {
final Map<String, String> env = iosDeploy.iosDeployEnv;
expect(env['PATH'].startsWith('/usr/bin'), true);
});
testWithoutContext('uninstallApp() calls ios-deploy with correct arguments and returns 0 on success', () async {
final List<String> args = <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
bundleId,
];
when(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(createMockProcess(exitCode: 0)));
final int exitCode = await iosDeploy.uninstallApp(
deviceId: deviceId,
bundleId: bundleId,
);
verify(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
));
expect(exitCode, 0);
});
testWithoutContext('uninstallApp() returns non-zero exit code when ios-deploy does the same', () async {
final List<String> args = <String>[
iosDeployPath,
'--id',
deviceId,
'--uninstall_only',
'--bundle_id',
bundleId,
];
when(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((Invocation invocation) => Future<Process>.value(createMockProcess(exitCode: 1)));
final int exitCode = await iosDeploy.uninstallApp(
deviceId: deviceId,
bundleId: bundleId,
);
verify(mockProcessManager.start(
args,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
));
expect(exitCode, 1);
});
});
}
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