Bundle ios dependencies (#34669)
This updates the flutter tool to cache binary files for ideviceinstaller, ios-deploy, libimobiledevice, and dynamically linked dependencies from Flutter's GCP bucket.
diff --git a/.cirrus.yml b/.cirrus.yml
index 204112c..6674fcc 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -21,7 +21,7 @@
fingerprint_script: echo $OS; cat bin/internal/engine.version
artifacts_cache:
folder: bin/cache/artifacts
- fingerprint_script: echo $OS; cat bin/internal/engine.version
+ fingerprint_script: echo $OS; cat bin/internal/*.version
setup_script: ./dev/bots/cirrus_setup.sh
matrix:
- name: docs
@@ -298,7 +298,7 @@
fingerprint_script: echo %OS% & type bin\internal\engine.version
artifacts_cache:
folder: bin\cache\artifacts
- fingerprint_script: echo %OS% & type bin\internal\engine.version
+ fingerprint_script: echo %OS% & type bin\internal\*.version
setup_script:
- flutter config --no-analytics
- flutter doctor -v
diff --git a/bin/internal/ideviceinstaller.version b/bin/internal/ideviceinstaller.version
new file mode 100644
index 0000000..90f1492
--- /dev/null
+++ b/bin/internal/ideviceinstaller.version
@@ -0,0 +1 @@
+ab9352110092cf651b5602301371cd00691c7e13
diff --git a/bin/internal/ios-deploy.version b/bin/internal/ios-deploy.version
new file mode 100644
index 0000000..7683618
--- /dev/null
+++ b/bin/internal/ios-deploy.version
@@ -0,0 +1 @@
+ea5583388ac0ca035f6b991fd7955bea6492c68c
diff --git a/bin/internal/libimobiledevice.version b/bin/internal/libimobiledevice.version
new file mode 100644
index 0000000..0fc801b
--- /dev/null
+++ b/bin/internal/libimobiledevice.version
@@ -0,0 +1 @@
+398c1208731cb887c64a31c2ae111048b079f80d
diff --git a/bin/internal/libplist.version b/bin/internal/libplist.version
new file mode 100644
index 0000000..973064b
--- /dev/null
+++ b/bin/internal/libplist.version
@@ -0,0 +1 @@
+17546f53ac1377b0d4f45a800aaec7366ba5b6a0
diff --git a/bin/internal/openssl.version b/bin/internal/openssl.version
new file mode 100644
index 0000000..400e36a
--- /dev/null
+++ b/bin/internal/openssl.version
@@ -0,0 +1 @@
+03da376ff7504c63a1d00d57cf41bd7b7e93ff65
diff --git a/bin/internal/usbmuxd.version b/bin/internal/usbmuxd.version
new file mode 100644
index 0000000..d77b5cf
--- /dev/null
+++ b/bin/internal/usbmuxd.version
@@ -0,0 +1 @@
+60109fdef47dfe0badfb558a6a2105e8fb23660a
diff --git a/dev/devicelab/lib/framework/adb.dart b/dev/devicelab/lib/framework/adb.dart
index e6765c6..f6db339 100644
--- a/dev/devicelab/lib/framework/adb.dart
+++ b/dev/devicelab/lib/framework/adb.dart
@@ -392,9 +392,34 @@
_workingDevice = allDevices[math.Random().nextInt(allDevices.length)];
}
+ // Returns the path to cached binaries relative to devicelab directory
+ String get _artifactDirPath {
+ return path.normalize(
+ path.join(
+ path.current,
+ '../../bin/cache/artifacts',
+ )
+ );
+ }
+
+ // Returns a colon-separated environment variable that contains the paths
+ // of linked libraries for idevice_id
+ Map<String, String> get _ideviceIdEnvironment {
+ final String libPath = const <String>[
+ 'libimobiledevice',
+ 'usbmuxd',
+ 'libplist',
+ 'openssl',
+ 'ideviceinstaller',
+ 'ios-deploy',
+ ].map((String packageName) => path.join(_artifactDirPath, packageName)).join(':');
+ return <String, String>{'DYLD_LIBRARY_PATH': libPath};
+ }
+
@override
Future<List<String>> discoverDevices() async {
- final List<String> iosDeviceIDs = LineSplitter.split(await eval('idevice_id', <String>['-l']))
+ final String ideviceIdPath = path.join(_artifactDirPath, 'libimobiledevice', 'idevice_id');
+ final List<String> iosDeviceIDs = LineSplitter.split(await eval(ideviceIdPath, <String>['-l'], environment: _ideviceIdEnvironment))
.map<String>((String line) => line.trim())
.where((String line) => line.isNotEmpty)
.toList();
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 65a8b63..191fd15 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -40,6 +40,14 @@
kernelWorkerSnapshot,
/// The root of the web implementation of the dart SDK.
flutterWebSdk,
+ iosDeploy,
+ ideviceinfo,
+ ideviceId,
+ idevicename,
+ idevicesyslog,
+ idevicescreenshot,
+ ideviceinstaller,
+ iproxy,
/// The root of the Linux desktop sources.
linuxDesktopPath,
/// The root of the Windows desktop sources.
@@ -93,6 +101,22 @@
return 'dartdevc.dart.snapshot';
case Artifact.kernelWorkerSnapshot:
return 'kernel_worker.dart.snapshot';
+ case Artifact.iosDeploy:
+ return 'ios-deploy';
+ case Artifact.ideviceinfo:
+ return 'ideviceinfo';
+ case Artifact.ideviceId:
+ return 'idevice_id';
+ case Artifact.idevicename:
+ return 'idevicename';
+ case Artifact.idevicesyslog:
+ return 'idevicesyslog';
+ case Artifact.idevicescreenshot:
+ return 'idevicescreenshot';
+ case Artifact.ideviceinstaller:
+ return 'ideviceinstaller';
+ case Artifact.iproxy:
+ return 'iproxy';
case Artifact.linuxDesktopPath:
if (platform != TargetPlatform.linux_x64) {
throw Exception('${getNameForTargetPlatform(platform)} does not support'
@@ -187,13 +211,26 @@
}
String _getIosArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) {
- final String engineDir = _getEngineArtifactsPath(platform, mode);
+ final String artifactFileName = _artifactToFileName(artifact);
switch (artifact) {
case Artifact.genSnapshot:
case Artifact.snapshotDart:
case Artifact.flutterFramework:
case Artifact.frontendServerSnapshotForEngineDartSdk:
- return fs.path.join(engineDir, _artifactToFileName(artifact));
+ final String engineDir = _getEngineArtifactsPath(platform, mode);
+ return fs.path.join(engineDir, artifactFileName);
+ case Artifact.ideviceId:
+ case Artifact.ideviceinfo:
+ case Artifact.idevicescreenshot:
+ case Artifact.idevicesyslog:
+ case Artifact.idevicename:
+ return cache.getArtifactDirectory('libimobiledevice').childFile(artifactFileName).path;
+ case Artifact.iosDeploy:
+ return cache.getArtifactDirectory('ios-deploy').childFile(artifactFileName).path;
+ case Artifact.ideviceinstaller:
+ return cache.getArtifactDirectory('ideviceinstaller').childFile(artifactFileName).path;
+ case Artifact.iproxy:
+ return cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName).path;
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
@@ -302,24 +339,25 @@
@override
String getArtifactPath(Artifact artifact, { TargetPlatform platform, BuildMode mode }) {
+ final String artifactFileName = _artifactToFileName(artifact);
switch (artifact) {
case Artifact.snapshotDart:
- return fs.path.join(_engineSrcPath, 'flutter', 'lib', 'snapshot', _artifactToFileName(artifact));
+ return fs.path.join(_engineSrcPath, 'flutter', 'lib', 'snapshot', artifactFileName);
case Artifact.genSnapshot:
return _genSnapshotPath();
case Artifact.flutterTester:
return _flutterTesterPath(platform);
case Artifact.isolateSnapshotData:
case Artifact.vmSnapshotData:
- return fs.path.join(engineOutPath, 'gen', 'flutter', 'lib', 'snapshot', _artifactToFileName(artifact));
+ return fs.path.join(engineOutPath, 'gen', 'flutter', 'lib', 'snapshot', artifactFileName);
case Artifact.platformKernelDill:
- return fs.path.join(_getFlutterPatchedSdkPath(mode), _artifactToFileName(artifact));
+ return fs.path.join(_getFlutterPatchedSdkPath(mode), artifactFileName);
case Artifact.platformLibrariesJson:
- return fs.path.join(_getFlutterPatchedSdkPath(mode), 'lib', _artifactToFileName(artifact));
+ return fs.path.join(_getFlutterPatchedSdkPath(mode), 'lib', artifactFileName);
case Artifact.flutterFramework:
- return fs.path.join(engineOutPath, _artifactToFileName(artifact));
+ return fs.path.join(engineOutPath, artifactFileName);
case Artifact.flutterMacOSFramework:
- return fs.path.join(engineOutPath, _artifactToFileName(artifact));
+ return fs.path.join(engineOutPath, artifactFileName);
case Artifact.flutterPatchedSdkPath:
// When using local engine always use [BuildMode.debug] regardless of
// what was specified in [mode] argument because local engine will
@@ -329,23 +367,35 @@
case Artifact.flutterWebSdk:
return _getFlutterWebSdkPath();
case Artifact.frontendServerSnapshotForEngineDartSdk:
- return fs.path.join(_hostEngineOutPath, 'gen', _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, 'gen', artifactFileName);
case Artifact.engineDartSdkPath:
return fs.path.join(_hostEngineOutPath, 'dart-sdk');
case Artifact.engineDartBinary:
- return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', artifactFileName);
case Artifact.dart2jsSnapshot:
- return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', artifactFileName);
case Artifact.dartdevcSnapshot:
- return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
+ return fs.path.join(dartSdkPath, 'bin', 'snapshots', artifactFileName);
case Artifact.kernelWorkerSnapshot:
- return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', artifactFileName);
+ case Artifact.ideviceId:
+ case Artifact.ideviceinfo:
+ case Artifact.idevicename:
+ case Artifact.idevicescreenshot:
+ case Artifact.idevicesyslog:
+ return cache.getArtifactDirectory('libimobiledevice').childFile(artifactFileName).path;
+ case Artifact.ideviceinstaller:
+ return cache.getArtifactDirectory('ideviceinstaller').childFile(artifactFileName).path;
+ case Artifact.iosDeploy:
+ return cache.getArtifactDirectory('ios-deploy').childFile(artifactFileName).path;
+ case Artifact.iproxy:
+ return cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName).path;
case Artifact.linuxDesktopPath:
- return fs.path.join(_hostEngineOutPath, _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.windowsDesktopPath:
- return fs.path.join(_hostEngineOutPath, _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, artifactFileName);
case Artifact.skyEnginePath:
- return fs.path.join(_hostEngineOutPath, 'gen', 'dart-pkg', _artifactToFileName(artifact));
+ return fs.path.join(_hostEngineOutPath, 'gen', 'dart-pkg', artifactFileName);
}
assert(false, 'Invalid artifact $artifact.');
return null;
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 47b2949..1dd5765 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -263,20 +263,26 @@
return result;
}
-bool exitsHappy(List<String> cli) {
+bool exitsHappy(
+ List<String> cli, {
+ Map<String, String> environment,
+}) {
_traceCommand(cli);
try {
- return processManager.runSync(cli).exitCode == 0;
+ return processManager.runSync(cli, environment: environment).exitCode == 0;
} catch (error) {
printTrace('$cli failed with $error');
return false;
}
}
-Future<bool> exitsHappyAsync(List<String> cli) async {
+Future<bool> exitsHappyAsync(
+ List<String> cli, {
+ Map<String, String> environment,
+}) async {
_traceCommand(cli);
try {
- return (await processManager.run(cli)).exitCode == 0;
+ return (await processManager.run(cli, environment: environment)).exitCode == 0;
} catch (error) {
printTrace('$cli failed with $error');
return false;
diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
index 09af158..0f359b5 100644
--- a/packages/flutter_tools/lib/src/base/user_messages.dart
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -145,44 +145,6 @@
'Once installed, run:\n'
' sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer';
- // Messages used in IOSValidator
- String get iOSIMobileDeviceMissing =>
- 'libimobiledevice and ideviceinstaller are not installed. To install with Brew, run:\n'
- ' brew update\n'
- ' brew install --HEAD usbmuxd\n'
- ' brew link usbmuxd\n'
- ' brew install --HEAD libimobiledevice\n'
- ' brew install ideviceinstaller';
- String get iOSIMobileDeviceBroken =>
- 'Verify that all connected devices have been paired with this computer in Xcode.\n'
- 'If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.\n'
- 'To update with Brew, run:\n'
- ' brew update\n'
- ' brew uninstall --ignore-dependencies libimobiledevice\n'
- ' brew uninstall --ignore-dependencies usbmuxd\n'
- ' brew install --HEAD usbmuxd\n'
- ' brew unlink usbmuxd\n'
- ' brew link usbmuxd\n'
- ' brew install --HEAD libimobiledevice\n'
- ' brew install ideviceinstaller';
- String get iOSDeviceInstallerMissing =>
- 'ideviceinstaller is not installed; this is used to discover connected iOS devices.\n'
- 'To install with Brew, run:\n'
- ' brew install --HEAD usbmuxd\n'
- ' brew link usbmuxd\n'
- ' brew install --HEAD libimobiledevice\n'
- ' brew install ideviceinstaller';
- String iOSDeployVersion(String version) => 'ios-deploy $version';
- String iOSDeployOutdated(String minVersion) =>
- 'ios-deploy out of date ($minVersion is required). To upgrade with Brew:\n'
- ' brew upgrade ios-deploy';
- String get iOSDeployMissing =>
- 'ios-deploy not installed. To install:\n'
- ' brew install ios-deploy';
- String get iOSBrewMissing =>
- 'Brew can be used to install tools for iOS device development.\n'
- 'Download brew at https://brew.sh/.';
-
// Messages used in CocoaPodsValidator
String cocoaPodsVersion(String version) => 'CocoaPods version $version';
String cocoaPodsUninitialized(String consequence) =>
@@ -206,9 +168,6 @@
'$consequence\n'
'To upgrade:\n'
'$upgradeInstructions';
- String get cocoaPodsBrewMissing =>
- 'Brew can be used to install CocoaPods.\n'
- 'Download brew at https://brew.sh/.';
// Messages used in VsCodeValidator
String vsCodeVersion(String version) => 'version $version';
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 6d2d13e..3a6039d 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -53,7 +53,7 @@
/// Artifacts required by all developments.
static const DevelopmentArtifact universal = DevelopmentArtifact._('universal');
- /// The vaulues of DevelopmentArtifacts.
+ /// The values of DevelopmentArtifacts.
static final List<DevelopmentArtifact> values = <DevelopmentArtifact>[
android,
iOS,
@@ -83,6 +83,9 @@
_artifacts.add(LinuxEngineArtifacts(this));
_artifacts.add(LinuxFuchsiaSDKArtifacts(this));
_artifacts.add(MacOSFuchsiaSDKArtifacts(this));
+ for (String artifactName in IosUsbArtifacts.artifactNames) {
+ _artifacts.add(IosUsbArtifacts(artifactName, this));
+ }
} else {
_artifacts.addAll(artifacts);
}
@@ -221,6 +224,22 @@
return getCacheArtifacts().childDirectory(name);
}
+ MapEntry<String, String> get dyLdLibEntry {
+ if (_dyLdLibEntry != null) {
+ return _dyLdLibEntry;
+ }
+ final List<String> paths = <String>[];
+ for (CachedArtifact artifact in _artifacts) {
+ final String currentPath = artifact.dyLdLibPath;
+ if (currentPath.isNotEmpty) {
+ paths.add(currentPath);
+ }
+ }
+ _dyLdLibEntry = MapEntry<String, String>('DYLD_LIBARY_PATH', paths.join(':'));
+ return _dyLdLibEntry;
+ }
+ MapEntry<String, String> _dyLdLibEntry;
+
/// The web sdk has to be co-located with the dart-sdk so that they can share source
/// code.
Directory getWebSdkDirectory() {
@@ -328,6 +347,9 @@
// artifact name.
String get stampName => name;
+ /// Returns a string to be set as environment DYLD_LIBARY_PATH variable
+ String get dyLdLibPath => '';
+
/// All development artifacts this cache provides.
final Set<DevelopmentArtifact> developmentArtifacts;
@@ -890,6 +912,39 @@
}
}
+/// Cached iOS/USB binary artifacts.
+class IosUsbArtifacts extends CachedArtifact {
+ IosUsbArtifacts(String name, Cache cache) : super(
+ name,
+ cache,
+ // This is universal to ensure every command checks for them first
+ const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
+ );
+
+ static const List<String> artifactNames = <String>[
+ 'libimobiledevice',
+ 'usbmuxd',
+ 'libplist',
+ 'openssl',
+ 'ideviceinstaller',
+ 'ios-deploy',
+ ];
+
+ @override
+ String get dyLdLibPath {
+ return cache.getArtifactDirectory(name).path;
+ }
+
+ @override
+ Future<void> updateInner() {
+ if (!platform.isMacOS) {
+ return Future<void>.value();
+ }
+ final Uri archiveUri = Uri.parse('$_storageBaseUrl/flutter_infra/ios-usb-dependencies/$name/$version/$name.zip');
+ return _downloadZipArchive('Downloading $name...', archiveUri, location);
+ }
+}
+
// Many characters are problematic in filenames, especially on Windows.
final Map<int, List<int>> _flattenNameSubstitutions = <int, List<int>>{
r'@'.codeUnitAt(0): '@@'.codeUnits,
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 533cfc6..ba5d910 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -87,9 +87,8 @@
FuchsiaWorkflow: () => FuchsiaWorkflow(),
GenSnapshot: () => const GenSnapshot(),
HotRunnerConfig: () => HotRunnerConfig(),
- IMobileDevice: () => const IMobileDevice(),
+ IMobileDevice: () => IMobileDevice(),
IOSSimulatorUtils: () => IOSSimulatorUtils(),
- IOSValidator: () => const IOSValidator(),
IOSWorkflow: () => const IOSWorkflow(),
KernelCompilerFactory: () => const KernelCompilerFactory(),
LinuxWorkflow: () => const LinuxWorkflow(),
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 58ba4de..c4aa3a6 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -72,8 +72,6 @@
GroupedValidator(<DoctorValidator>[androidValidator, androidLicenseValidator]),
if (iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
GroupedValidator(<DoctorValidator>[xcodeValidator, cocoapodsValidator]),
- if (iosWorkflow.appliesToHostPlatform)
- iosValidator,
if (webWorkflow.appliesToHostPlatform)
const WebValidator(),
// Add desktop doctors to workflow if the flag is enabled.
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 3ac80c5..7007daf 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -7,6 +7,7 @@
import 'package:meta/meta.dart';
import '../application_package.dart';
+import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
@@ -23,10 +24,6 @@
import 'ios_workflow.dart';
import 'mac.dart';
-const String _kIdeviceinstallerInstructions =
- 'To work with iOS devices, please install ideviceinstaller. To install, run:\n'
- 'brew install ideviceinstaller.';
-
class IOSDeploy {
const IOSDeploy();
@@ -37,9 +34,15 @@
@required String bundlePath,
@required List<String> launchArguments,
}) async {
- final List<String> launchCommand = <String>[
+ final String iosDeployPath = artifacts.getArtifactPath(Artifact.iosDeploy, platform: TargetPlatform.ios);
+ // TODO(fujino): remove fallback once g3 updated
+ const List<String> fallbackIosDeployPath = <String>[
'/usr/bin/env',
'ios-deploy',
+ ];
+ final List<String> commandList = iosDeployPath != null ? <String>[iosDeployPath] : fallbackIosDeployPath;
+ final List<String> launchCommand = <String>[
+ ...commandList,
'--id',
deviceId,
'--bundle',
@@ -61,6 +64,7 @@
// it.
final Map<String, String> iosDeployEnv = Map<String, String>.from(platform.environment);
iosDeployEnv['PATH'] = '/usr/bin:${iosDeployEnv['PATH']}';
+ iosDeployEnv.addEntries(<MapEntry<String, String>>[cache.dyLdLibEntry]);
return await runCommandAndStreamOutput(
launchCommand,
@@ -120,8 +124,20 @@
platformType: PlatformType.ios,
ephemeral: true,
) {
- _installerPath = _checkForCommand('ideviceinstaller');
- _iproxyPath = _checkForCommand('iproxy');
+ if (platform.isMacOS) {
+ printError('Cannot control iOS devices or simulators. ideviceinstaller and iproxy are not available on your platform.');
+ _installerPath = null;
+ _iproxyPath = null;
+ return;
+ }
+ _installerPath = artifacts.getArtifactPath(
+ Artifact.ideviceinstaller,
+ platform: TargetPlatform.ios
+ ) ?? 'ideviceinstaller'; // TODO(fujino): remove fallback once g3 updated
+ _iproxyPath = artifacts.getArtifactPath(
+ Artifact.iproxy,
+ platform: TargetPlatform.ios
+ ) ?? 'iproxy'; // TODO(fujino): remove fallback once g3 updated
}
String _installerPath;
@@ -173,23 +189,6 @@
return devices;
}
- static String _checkForCommand(
- String command, [
- String macInstructions = _kIdeviceinstallerInstructions,
- ]) {
- try {
- command = runCheckedSync(<String>['which', command]).trim();
- } catch (e) {
- if (platform.isMacOS) {
- printError('$command not found. $macInstructions');
- } else {
- printError('Cannot control iOS devices or simulators. $command is not available on your platform.');
- }
- return null;
- }
- return command;
- }
-
@override
Future<bool> isAppInstalled(ApplicationPackage app) async {
try {
@@ -589,12 +588,17 @@
while (!connected) {
printTrace('attempting to forward device port $devicePort to host port $hostPort');
// Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
- process = await runCommand(<String>[
- device._iproxyPath,
- hostPort.toString(),
- devicePort.toString(),
- device.id,
- ]);
+ process = await runCommand(
+ <String>[
+ device._iproxyPath,
+ hostPort.toString(),
+ devicePort.toString(),
+ device.id,
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry],
+ ),
+ );
// TODO(ianh): This is a flakey race condition, https://github.com/libimobiledevice/libimobiledevice/issues/674
connected = !await process.stdout.isEmpty.timeout(_kiProxyPortForwardTimeout, onTimeout: () => false);
if (!connected) {
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index cf95c30..f21dffd 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -2,21 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:async';
-
import '../base/context.dart';
-import '../base/os.dart';
import '../base/platform.dart';
-import '../base/process.dart';
-import '../base/user_messages.dart';
-import '../base/version.dart';
import '../doctor.dart';
import '../macos/xcode.dart';
-import 'mac.dart';
import 'plist_utils.dart' as plist;
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
-IOSValidator get iosValidator => context.get<IOSValidator>();
class IOSWorkflow implements Workflow {
const IOSWorkflow();
@@ -40,86 +32,3 @@
return plist.getValueFromFile(path, key);
}
}
-
-class IOSValidator extends DoctorValidator {
-
- const IOSValidator() : super('iOS tools - develop for iOS devices');
-
- Future<bool> get hasIDeviceInstaller => exitsHappyAsync(<String>['ideviceinstaller', '-h']);
-
- Future<bool> get hasIosDeploy => exitsHappyAsync(<String>['ios-deploy', '--version']);
-
- String get iosDeployMinimumVersion => '1.9.4';
-
- // ios-deploy <= v1.9.3 declares itself as v2.0.0
- List<String> get iosDeployBadVersions => <String>['2.0.0'];
-
- Future<String> get iosDeployVersionText async => (await runAsync(<String>['ios-deploy', '--version'])).processResult.stdout.replaceAll('\n', '');
-
- bool get hasHomebrew => os.which('brew') != null;
-
- Future<String> get macDevMode async => (await runAsync(<String>['DevToolsSecurity', '-status'])).processResult.stdout;
-
- Future<bool> get _iosDeployIsInstalledAndMeetsVersionCheck async {
- if (!await hasIosDeploy)
- return false;
- try {
- final Version version = Version.parse(await iosDeployVersionText);
- return version >= Version.parse(iosDeployMinimumVersion)
- && !iosDeployBadVersions.map((String v) => Version.parse(v)).contains(version);
- } on FormatException catch (_) {
- return false;
- }
- }
-
- // Change this value if the number of checks for packages needed for installation changes
- static const int totalChecks = 4;
-
- @override
- Future<ValidationResult> validate() async {
- final List<ValidationMessage> messages = <ValidationMessage>[];
- ValidationType packageManagerStatus = ValidationType.installed;
-
- int checksFailed = 0;
-
- if (!iMobileDevice.isInstalled) {
- checksFailed += 3;
- packageManagerStatus = ValidationType.partial;
- messages.add(ValidationMessage.error(userMessages.iOSIMobileDeviceMissing));
- } else if (!await iMobileDevice.isWorking) {
- checksFailed += 2;
- packageManagerStatus = ValidationType.partial;
- messages.add(ValidationMessage.error(userMessages.iOSIMobileDeviceBroken));
- } else if (!await hasIDeviceInstaller) {
- checksFailed += 1;
- packageManagerStatus = ValidationType.partial;
- messages.add(ValidationMessage.error(userMessages.iOSDeviceInstallerMissing));
- }
-
- final bool iHasIosDeploy = await hasIosDeploy;
-
- // Check ios-deploy is installed at meets version requirements.
- if (iHasIosDeploy) {
- messages.add(ValidationMessage(userMessages.iOSDeployVersion(await iosDeployVersionText)));
- }
- if (!await _iosDeployIsInstalledAndMeetsVersionCheck) {
- packageManagerStatus = ValidationType.partial;
- if (iHasIosDeploy) {
- messages.add(ValidationMessage.error(userMessages.iOSDeployOutdated(iosDeployMinimumVersion)));
- } else {
- checksFailed += 1;
- messages.add(ValidationMessage.error(userMessages.iOSDeployMissing));
- }
- }
-
- // If one of the checks for the packages failed, we may need brew so that we can install
- // the necessary packages. If they're all there, however, we don't even need it.
- if (checksFailed == totalChecks)
- packageManagerStatus = ValidationType.missing;
- if (checksFailed > 0 && !hasHomebrew) {
- messages.add(ValidationMessage.hint(userMessages.iOSBrewMissing));
- }
-
- return ValidationResult(packageManagerStatus, messages);
- }
-}
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index dcc7f5a..2bcdd59 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -7,6 +7,7 @@
import 'package:meta/meta.dart';
import '../application_package.dart';
+import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
@@ -42,35 +43,99 @@
}
class IMobileDevice {
- const IMobileDevice();
+ IMobileDevice()
+ : _ideviceIdPath = artifacts.getArtifactPath(Artifact.ideviceId, platform: TargetPlatform.ios)
+ ?? 'idevice_id', // TODO(fujino): remove fallback once g3 updated
+ _ideviceinfoPath = artifacts.getArtifactPath(Artifact.ideviceinfo, platform: TargetPlatform.ios)
+ ?? 'ideviceinfo', // TODO(fujino): remove fallback once g3 updated
+ _idevicenamePath = artifacts.getArtifactPath(Artifact.idevicename, platform: TargetPlatform.ios)
+ ?? 'idevicename', // TODO(fujino): remove fallback once g3 updated
+ _idevicesyslogPath = artifacts.getArtifactPath(Artifact.idevicesyslog, platform: TargetPlatform.ios)
+ ?? 'idevicesyslog', // TODO(fujino): remove fallback once g3 updated
+ _idevicescreenshotPath = artifacts.getArtifactPath(Artifact.idevicescreenshot, platform: TargetPlatform.ios)
+ ?? 'idevicescreenshot' { // TODO(fujino): remove fallback once g3 updated
+ }
+ final String _ideviceIdPath;
+ final String _ideviceinfoPath;
+ final String _idevicenamePath;
+ final String _idevicesyslogPath;
+ final String _idevicescreenshotPath;
- bool get isInstalled => exitsHappy(<String>['idevice_id', '-h']);
+ bool get isInstalled {
+ _isInstalled ??= exitsHappy(
+ <String>[
+ _ideviceIdPath,
+ '-h'
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ ),
+ );
+ return _isInstalled;
+ }
+ bool _isInstalled;
/// Returns true if libimobiledevice is installed and working as expected.
///
/// Older releases of libimobiledevice fail to work with iOS 10.3 and above.
Future<bool> get isWorking async {
- if (!isInstalled)
- return false;
+ if (_isWorking != null) {
+ return _isWorking;
+ }
+ if (!isInstalled) {
+ _isWorking = false;
+ return _isWorking;
+ }
// If usage info is printed in a hyphenated id, we need to update.
const String fakeIphoneId = '00008020-001C2D903C42002E';
- final ProcessResult ideviceResult = (await runAsync(<String>['ideviceinfo', '-u', fakeIphoneId])).processResult;
+ final Map<String, String> executionEnv = Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ );
+ final ProcessResult ideviceResult = (await runAsync(
+ <String>[
+ _ideviceinfoPath,
+ '-u',
+ fakeIphoneId
+ ],
+ environment: executionEnv,
+ )).processResult;
if (ideviceResult.stdout.contains('Usage: ideviceinfo')) {
- return false;
+ _isWorking = false;
+ return _isWorking;
}
// If no device is attached, we're unable to detect any problems. Assume all is well.
- final ProcessResult result = (await runAsync(<String>['idevice_id', '-l'])).processResult;
- if (result.exitCode == 0 && result.stdout.isEmpty)
- return true;
-
- // Check that we can look up the names of any attached devices.
- return await exitsHappyAsync(<String>['idevicename']);
+ final ProcessResult result = (await runAsync(
+ <String>[
+ _ideviceIdPath,
+ '-l',
+ ],
+ environment: executionEnv,
+ )).processResult;
+ if (result.exitCode == 0 && result.stdout.isEmpty) {
+ _isWorking = true;
+ } else {
+ // Check that we can look up the names of any attached devices.
+ _isWorking = await exitsHappyAsync(
+ <String>[_idevicenamePath],
+ environment: executionEnv,
+ );
+ }
+ return _isWorking;
}
+ bool _isWorking;
Future<String> getAvailableDeviceIDs() async {
try {
- final ProcessResult result = await processManager.run(<String>['idevice_id', '-l']);
+ final ProcessResult result = await processManager.run(
+ <String>[
+ _ideviceIdPath,
+ '-l'
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ ),
+ );
if (result.exitCode != 0)
throw ToolExit('idevice_id returned an error:\n${result.stderr}');
return result.stdout;
@@ -81,7 +146,18 @@
Future<String> getInfoForDevice(String deviceID, String key) async {
try {
- final ProcessResult result = await processManager.run(<String>['ideviceinfo', '-u', deviceID, '-k', key]);
+ final ProcessResult result = await processManager.run(
+ <String>[
+ _ideviceinfoPath,
+ '-u',
+ deviceID,
+ '-k',
+ key
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ ),
+ );
if (result.exitCode == 255 && result.stdout != null && result.stdout.contains('No device found'))
throw IOSDeviceNotFoundError('ideviceinfo could not find device:\n${result.stdout}');
if (result.exitCode != 0)
@@ -93,11 +169,30 @@
}
/// Starts `idevicesyslog` and returns the running process.
- Future<Process> startLogger(String deviceID) => runCommand(<String>['idevicesyslog', '-u', deviceID]);
+ Future<Process> startLogger(String deviceID) {
+ return runCommand(
+ <String>[
+ _idevicesyslogPath,
+ '-u',
+ deviceID,
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ ),
+ );
+ }
/// Captures a screenshot to the specified outputFile.
Future<void> takeScreenshot(File outputFile) {
- return runCheckedAsync(<String>['idevicescreenshot', outputFile.path]);
+ return runCheckedAsync(
+ <String>[
+ _idevicescreenshotPath,
+ outputFile.path
+ ],
+ environment: Map<String, String>.fromEntries(
+ <MapEntry<String, String>>[cache.dyLdLibEntry]
+ ),
+ );
}
}
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index 9462395..9729387 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -30,11 +30,11 @@
Ensure that the output of 'pod --version' contains only digits and . to be recognized by Flutter.''';
const String cocoaPodsInstallInstructions = '''
- brew install cocoapods
+ sudo gem install cocoapods
pod setup''';
const String cocoaPodsUpgradeInstructions = '''
- brew upgrade cocoapods
+ sudo gem install cocoapods
pod setup''';
CocoaPods get cocoaPods => context.get<CocoaPods>();
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
index 615c3f5..aaee745 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart
@@ -5,7 +5,6 @@
import 'dart:async';
import '../base/context.dart';
-import '../base/os.dart';
import '../base/user_messages.dart';
import '../doctor.dart';
import 'cocoapods.dart';
@@ -15,8 +14,6 @@
class CocoaPodsValidator extends DoctorValidator {
const CocoaPodsValidator() : super('CocoaPods subvalidator');
- bool get hasHomebrew => os.which('brew') != null;
-
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
@@ -48,11 +45,6 @@
}
}
- // Only check/report homebrew status if CocoaPods isn't installed.
- if (status == ValidationType.missing && !hasHomebrew) {
- messages.add(ValidationMessage.hint(userMessages.cocoaPodsBrewMissing));
- }
-
return ValidationResult(status, messages);
}
-}
\ No newline at end of file
+}
diff --git a/packages/flutter_tools/test/cache_test.dart b/packages/flutter_tools/test/cache_test.dart
index a95964a..64a7e58 100644
--- a/packages/flutter_tools/test/cache_test.dart
+++ b/packages/flutter_tools/test/cache_test.dart
@@ -107,6 +107,22 @@
verifyNever(artifact1.update(<DevelopmentArtifact>{}));
verify(artifact2.update(<DevelopmentArtifact>{}));
});
+ testUsingContext('getter dyLdLibEntry concatenates the output of each artifact\'s dyLdLibEntry getter', () async {
+ final CachedArtifact artifact1 = MockCachedArtifact();
+ final CachedArtifact artifact2 = MockCachedArtifact();
+ final CachedArtifact artifact3 = MockCachedArtifact();
+ when(artifact1.dyLdLibPath).thenReturn('/path/to/alpha:/path/to/beta');
+ when(artifact2.dyLdLibPath).thenReturn('/path/to/gamma:/path/to/delta:/path/to/epsilon');
+ when(artifact3.dyLdLibPath).thenReturn(''); // Empty output
+ final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2, artifact3]);
+ expect(cache.dyLdLibEntry.key, 'DYLD_LIBARY_PATH');
+ expect(
+ cache.dyLdLibEntry.value,
+ '/path/to/alpha:/path/to/beta:/path/to/gamma:/path/to/delta:/path/to/epsilon',
+ );
+ }, overrides: <Type, Generator>{
+ Cache: ()=> mockCache,
+ });
testUsingContext('failed storage.googleapis.com download shows China warning', () async {
final CachedArtifact artifact1 = MockCachedArtifact();
final CachedArtifact artifact2 = MockCachedArtifact();
diff --git a/packages/flutter_tools/test/ios/code_signing_test.dart b/packages/flutter_tools/test/ios/code_signing_test.dart
index 9d381a0..ecea51f 100644
--- a/packages/flutter_tools/test/ios/code_signing_test.dart
+++ b/packages/flutter_tools/test/ios/code_signing_test.dart
@@ -425,7 +425,7 @@
final Map<String, String> signingConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app);
expect(
- testLogger.errorText,
+ testLogger.errorText.replaceAll('\n', ' '),
contains('Saved signing certificate "iPhone Developer: Invalid Profile" is not a valid development certificate'),
);
expect(
diff --git a/packages/flutter_tools/test/ios/ios_workflow_test.dart b/packages/flutter_tools/test/ios/ios_workflow_test.dart
deleted file mode 100644
index a2e3745..0000000
--- a/packages/flutter_tools/test/ios/ios_workflow_test.dart
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2017 The Chromium 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:file/memory.dart';
-import 'package:flutter_tools/src/base/file_system.dart';
-import 'package:flutter_tools/src/base/io.dart';
-import 'package:flutter_tools/src/doctor.dart';
-import 'package:flutter_tools/src/ios/ios_workflow.dart';
-import 'package:flutter_tools/src/ios/mac.dart';
-import 'package:mockito/mockito.dart';
-import 'package:process/process.dart';
-
-import '../src/common.dart';
-import '../src/context.dart';
-
-void main() {
- group('iOS Workflow validation', () {
- MockIMobileDevice iMobileDevice;
- MockIMobileDevice iMobileDeviceUninstalled;
- MockProcessManager processManager;
- FileSystem fs;
-
- setUp(() {
- iMobileDevice = MockIMobileDevice();
- iMobileDeviceUninstalled = MockIMobileDevice(isInstalled: false);
- processManager = MockProcessManager();
- fs = MemoryFileSystem();
- });
-
- testUsingContext('Emit missing status when nothing is installed', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(
- hasHomebrew: false,
- hasIosDeploy: false,
- hasIDeviceInstaller: false,
- iosDeployVersionText: '0.0.0',
- );
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.missing);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => iMobileDeviceUninstalled,
- });
-
- testUsingContext('Emits installed status when homebrew not installed, but not needed', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(hasHomebrew: false);
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.installed);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => iMobileDevice,
- });
-
- testUsingContext('Emits partial status when libimobiledevice is not installed', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget();
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => MockIMobileDevice(isInstalled: false, isWorking: false),
- });
-
- testUsingContext('Emits partial status when libimobiledevice is installed but not working', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget();
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => MockIMobileDevice(isWorking: false),
- });
-
- testUsingContext('Emits partial status when libimobiledevice is installed but not working', () async {
- when(processManager.run(
- <String>['ideviceinfo', '-u', '00008020-001C2D903C42002E'],
- workingDirectory: anyNamed('workingDirectory'),
- environment: anyNamed('environment')),
- ).thenAnswer((Invocation _) async {
- final MockProcessResult result = MockProcessResult();
- when<String>(result.stdout).thenReturn(r'''
-Usage: ideviceinfo [OPTIONS]
-Show information about a connected device.
-
- -d, --debug enable communication debugging
- -s, --simple use a simple connection to avoid auto-pairing with the device
- -u, --udid UDID target specific device by its 40-digit device UDID
- -q, --domain NAME set domain of query to NAME. Default: None
- -k, --key NAME only query key specified by NAME. Default: All keys.
- -x, --xml output information as xml plist instead of key/value pairs
- -h, --help prints usage information
- ''');
- return null;
- });
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget();
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- ProcessManager: () => processManager,
- });
-
-
- testUsingContext('Emits partial status when ios-deploy is not installed', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(hasIosDeploy: false);
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => iMobileDevice,
- });
-
- testUsingContext('Emits partial status when ios-deploy version is too low', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(iosDeployVersionText: '1.8.0');
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => iMobileDevice,
- });
-
- testUsingContext('Emits partial status when ios-deploy version is a known bad version', () async {
- final IOSWorkflowTestTarget workflow = IOSWorkflowTestTarget(iosDeployVersionText: '2.0.0');
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.partial);
- }, overrides: <Type, Generator>{
- IMobileDevice: () => iMobileDevice,
- });
-
- testUsingContext('Succeeds when all checks pass', () async {
- final ValidationResult result = await IOSWorkflowTestTarget().validate();
- expect(result.type, ValidationType.installed);
- }, overrides: <Type, Generator>{
- FileSystem: () => fs,
- IMobileDevice: () => iMobileDevice,
- ProcessManager: () => processManager,
- });
- });
-}
-
-final ProcessResult exitsHappy = ProcessResult(
- 1, // pid
- 0, // exitCode
- '', // stdout
- '', // stderr
-);
-
-class MockIMobileDevice extends IMobileDevice {
- MockIMobileDevice({
- this.isInstalled = true,
- bool isWorking = true,
- }) : isWorking = Future<bool>.value(isWorking);
-
- @override
- final bool isInstalled;
-
- @override
- final Future<bool> isWorking;
-}
-
-class MockProcessManager extends Mock implements ProcessManager {}
-class MockProcessResult extends Mock implements ProcessResult {}
-
-class IOSWorkflowTestTarget extends IOSValidator {
- IOSWorkflowTestTarget({
- this.hasHomebrew = true,
- bool hasIosDeploy = true,
- String iosDeployVersionText = '1.9.4',
- bool hasIDeviceInstaller = true,
- }) : hasIosDeploy = Future<bool>.value(hasIosDeploy),
- iosDeployVersionText = Future<String>.value(iosDeployVersionText),
- hasIDeviceInstaller = Future<bool>.value(hasIDeviceInstaller);
-
- @override
- final bool hasHomebrew;
-
- @override
- final Future<bool> hasIosDeploy;
-
- @override
- final Future<String> iosDeployVersionText;
-
- @override
- final Future<bool> hasIDeviceInstaller;
-}
diff --git a/packages/flutter_tools/test/ios/mac_test.dart b/packages/flutter_tools/test/ios/mac_test.dart
index 8124ed8..de77d6e 100644
--- a/packages/flutter_tools/test/ios/mac_test.dart
+++ b/packages/flutter_tools/test/ios/mac_test.dart
@@ -9,6 +9,8 @@
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
@@ -22,6 +24,8 @@
Platform: _kNoColorTerminalPlatform,
};
+class MockArtifacts extends Mock implements Artifacts {}
+class MockCache extends Mock implements Cache {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockFile extends Mock implements File {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
@@ -32,41 +36,80 @@
final FakePlatform osx = FakePlatform.fromPlatform(const LocalPlatform())
..operatingSystem = 'macos';
MockProcessManager mockProcessManager;
+ final String libimobiledevicePath = fs.path.join('bin', 'cache', 'artifacts', 'libimobiledevice');
+ final String ideviceIdPath = fs.path.join(libimobiledevicePath, 'idevice_id');
+ final String ideviceInfoPath = fs.path.join(libimobiledevicePath, 'ideviceinfo');
+ final String idevicescreenshotPath = fs.path.join(libimobiledevicePath, 'idevicescreenshot');
+ MockArtifacts mockArtifacts;
+ MockCache mockCache;
setUp(() {
mockProcessManager = MockProcessManager();
+ mockCache = MockCache();
+ mockArtifacts = MockArtifacts();
+ when(mockArtifacts.getArtifactPath(Artifact.ideviceId, platform: anyNamed('platform'))).thenReturn(ideviceIdPath);
+ when(mockCache.dyLdLibEntry).thenReturn(
+ MapEntry<String, String>('DYLD_LIBRARY_PATH', libimobiledevicePath)
+ );
+ });
+
+ testUsingContext('isWorking returns false if libimobiledevice is not installed', () async {
+ when(mockProcessManager.runSync(
+ <String>[ideviceIdPath, '-h'], environment: anyNamed('environment')
+ )).thenReturn(ProcessResult(123, 1, '', ''));
+ expect(await iMobileDevice.isWorking, false);
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ Artifacts: () => mockArtifacts,
});
testUsingContext('getAvailableDeviceIDs throws ToolExit when libimobiledevice is not installed', () async {
- when(mockProcessManager.run(<String>['idevice_id', '-l']))
- .thenThrow(const ProcessException('idevice_id', <String>['-l']));
+ when(mockProcessManager.run(
+ <String>[ideviceIdPath, '-l'],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
+ )).thenThrow(ProcessException(ideviceIdPath, <String>['-l']));
expect(() async => await iMobileDevice.getAvailableDeviceIDs(), throwsToolExit());
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
+ Cache: () => mockCache,
+ Artifacts: () => mockArtifacts,
});
testUsingContext('getAvailableDeviceIDs throws ToolExit when idevice_id returns non-zero', () async {
- when(mockProcessManager.run(<String>['idevice_id', '-l']))
- .thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 1, '', 'Sad today')));
+ when(mockProcessManager.run(
+ <String>[ideviceIdPath, '-l'],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
+ )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 1, '', 'Sad today')));
expect(() async => await iMobileDevice.getAvailableDeviceIDs(), throwsToolExit());
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
+ Cache: () => mockCache,
+ Artifacts: () => mockArtifacts,
});
testUsingContext('getAvailableDeviceIDs returns idevice_id output when installed', () async {
- when(mockProcessManager.run(<String>['idevice_id', '-l']))
- .thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, 'foo', '')));
+ when(mockProcessManager.run(
+ <String>[ideviceIdPath, '-l'],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
+ )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 0, 'foo', '')));
expect(await iMobileDevice.getAvailableDeviceIDs(), 'foo');
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
+ Cache: () => mockCache,
+ Artifacts: () => mockArtifacts,
});
testUsingContext('getInfoForDevice throws IOSDeviceNotFoundError when ideviceinfo returns specific error code and message', () async {
- when(mockProcessManager.run(<String>['ideviceinfo', '-u', 'foo', '-k', 'bar']))
- .thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 255, 'No device found with udid foo, is it plugged in?', '')));
+ when(mockArtifacts.getArtifactPath(Artifact.ideviceinfo, platform: anyNamed('platform'))).thenReturn(ideviceInfoPath);
+ when(mockProcessManager.run(
+ <String>[ideviceInfoPath, '-u', 'foo', '-k', 'bar'],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
+ )).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(1, 255, 'No device found with udid foo, is it plugged in?', '')));
expect(() async => await iMobileDevice.getInfoForDevice('foo', 'bar'), throwsA(isInstanceOf<IOSDeviceNotFoundError>()));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
+ Cache: () => mockCache,
+ Artifacts: () => mockArtifacts,
});
group('screenshot', () {
@@ -77,14 +120,15 @@
setUp(() {
mockProcessManager = MockProcessManager();
mockOutputFile = MockFile();
+ when(mockArtifacts.getArtifactPath(Artifact.idevicescreenshot, platform: anyNamed('platform'))).thenReturn(idevicescreenshotPath);
});
testUsingContext('error if idevicescreenshot is not installed', () async {
when(mockOutputFile.path).thenReturn(outputPath);
// Let `idevicescreenshot` fail with exit code 1.
- when(mockProcessManager.run(<String>['idevicescreenshot', outputPath],
- environment: null,
+ when(mockProcessManager.run(<String>[idevicescreenshotPath, outputPath],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
workingDirectory: null,
)).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(4, 1, '', '')));
@@ -92,20 +136,23 @@
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => osx,
+ Cache: () => mockCache,
});
testUsingContext('idevicescreenshot captures and returns screenshot', () async {
when(mockOutputFile.path).thenReturn(outputPath);
- when(mockProcessManager.run(any, environment: null, workingDirectory: null)).thenAnswer(
+ when(mockProcessManager.run(any, environment: anyNamed('environment'), workingDirectory: null)).thenAnswer(
(Invocation invocation) => Future<ProcessResult>.value(ProcessResult(4, 0, '', '')));
await iMobileDevice.takeScreenshot(mockOutputFile);
- verify(mockProcessManager.run(<String>['idevicescreenshot', outputPath],
- environment: null,
+ verify(mockProcessManager.run(<String>[idevicescreenshotPath, outputPath],
+ environment: <String, String>{'DYLD_LIBRARY_PATH': libimobiledevicePath},
workingDirectory: null,
));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
+ Cache: () => mockCache,
+ Artifacts: () => mockArtifacts,
});
});
});
diff --git a/packages/flutter_tools/test/macos/cocoapods_validator_test.dart b/packages/flutter_tools/test/macos/cocoapods_validator_test.dart
index bd49fc1..996bd35 100644
--- a/packages/flutter_tools/test/macos/cocoapods_validator_test.dart
+++ b/packages/flutter_tools/test/macos/cocoapods_validator_test.dart
@@ -23,7 +23,7 @@
});
testUsingContext('Emits installed status when CocoaPods is installed', () async {
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
+ const CocoaPodsValidator workflow = CocoaPodsValidator();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.installed);
}, overrides: <Type, Generator>{
@@ -33,7 +33,7 @@
testUsingContext('Emits missing status when CocoaPods is not installed', () async {
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
+ const CocoaPodsValidator workflow = CocoaPodsValidator();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.missing);
}, overrides: <Type, Generator>{
@@ -43,7 +43,7 @@
testUsingContext('Emits partial status when CocoaPods is installed with unknown version', () async {
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.unknownVersion);
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
+ const CocoaPodsValidator workflow = CocoaPodsValidator();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
@@ -52,7 +52,7 @@
testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
+ const CocoaPodsValidator workflow = CocoaPodsValidator();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
@@ -62,30 +62,13 @@
testUsingContext('Emits partial status when CocoaPods version is too low', () async {
when(cocoaPods.evaluateCocoaPodsInstallation)
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
+ const CocoaPodsValidator workflow = CocoaPodsValidator();
final ValidationResult result = await workflow.validate();
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
CocoaPods: () => cocoaPods,
});
-
- testUsingContext('Emits installed status when homebrew not installed, but not needed', () async {
- final CocoaPodsTestTarget workflow = CocoaPodsTestTarget(hasHomebrew: false);
- final ValidationResult result = await workflow.validate();
- expect(result.type, ValidationType.installed);
- }, overrides: <Type, Generator>{
- CocoaPods: () => cocoaPods,
- });
});
}
class MockCocoaPods extends Mock implements CocoaPods {}
-
-class CocoaPodsTestTarget extends CocoaPodsValidator {
- CocoaPodsTestTarget({
- this.hasHomebrew = true,
- });
-
- @override
- final bool hasHomebrew;
-}