forked from firka/flutter
Reland bundle ios deps (#36093)
This updates the flutter tool cache to download binary files for ideviceinstaller, ios-deploy, libimobiledevice, and dynamically linked dependencies from Flutter's GCP bucket.
This commit is contained in:
committed by
GitHub
parent
e3ee5c6bbb
commit
102ab1e6d9
@@ -21,7 +21,7 @@ task:
|
||||
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 @@ task:
|
||||
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
|
||||
|
||||
1
bin/internal/ideviceinstaller.version
Normal file
1
bin/internal/ideviceinstaller.version
Normal file
@@ -0,0 +1 @@
|
||||
ab9352110092cf651b5602301371cd00691c7e13
|
||||
1
bin/internal/ios-deploy.version
Normal file
1
bin/internal/ios-deploy.version
Normal file
@@ -0,0 +1 @@
|
||||
ea5583388ac0ca035f6b991fd7955bea6492c68c
|
||||
1
bin/internal/libimobiledevice.version
Normal file
1
bin/internal/libimobiledevice.version
Normal file
@@ -0,0 +1 @@
|
||||
398c1208731cb887c64a31c2ae111048b079f80d
|
||||
1
bin/internal/libplist.version
Normal file
1
bin/internal/libplist.version
Normal file
@@ -0,0 +1 @@
|
||||
17546f53ac1377b0d4f45a800aaec7366ba5b6a0
|
||||
1
bin/internal/openssl.version
Normal file
1
bin/internal/openssl.version
Normal file
@@ -0,0 +1 @@
|
||||
03da376ff7504c63a1d00d57cf41bd7b7e93ff65
|
||||
1
bin/internal/usbmuxd.version
Normal file
1
bin/internal/usbmuxd.version
Normal file
@@ -0,0 +1 @@
|
||||
60109fdef47dfe0badfb558a6a2105e8fb23660a
|
||||
@@ -392,9 +392,34 @@ class IosDeviceDiscovery implements DeviceDiscovery {
|
||||
_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();
|
||||
|
||||
@@ -40,6 +40,14 @@ enum Artifact {
|
||||
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 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
|
||||
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 @@ class CachedArtifacts extends Artifacts {
|
||||
}
|
||||
|
||||
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 @@ class LocalEngineArtifacts extends Artifacts {
|
||||
|
||||
@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 @@ class LocalEngineArtifacts extends Artifacts {
|
||||
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;
|
||||
|
||||
@@ -263,20 +263,26 @@ Future<RunResult> runCheckedAsync(
|
||||
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;
|
||||
|
||||
@@ -145,44 +145,6 @@ class UserMessages {
|
||||
'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 @@ class UserMessages {
|
||||
'$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';
|
||||
|
||||
@@ -53,7 +53,7 @@ class DevelopmentArtifact {
|
||||
/// 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 @@ class Cache {
|
||||
_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 @@ class Cache {
|
||||
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_LIBRARY_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 @@ abstract class CachedArtifact {
|
||||
// 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 @@ class MacOSFuchsiaSDKArtifacts extends _FuchsiaSDKArtifacts {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -87,9 +87,8 @@ Future<T> runInContext<T>(
|
||||
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(),
|
||||
|
||||
@@ -72,8 +72,6 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
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.
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'dart:async';
|
||||
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 'code_signing.dart';
|
||||
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 @@ class IOSDeploy {
|
||||
@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 @@ class IOSDeploy {
|
||||
// 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 @@ class IOSDevice extends Device {
|
||||
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 @@ class IOSDevice extends Device {
|
||||
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 @@ class _IOSDevicePortForwarder extends DevicePortForwarder {
|
||||
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) {
|
||||
|
||||
@@ -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 @@ class IOSWorkflow implements Workflow {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'dart:async';
|
||||
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 IOSDeviceNotFoundError implements Exception {
|
||||
}
|
||||
|
||||
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 @@ class IMobileDevice {
|
||||
|
||||
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 @@ class IMobileDevice {
|
||||
}
|
||||
|
||||
/// 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]
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ const String unknownCocoaPodsConsequence = '''
|
||||
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>();
|
||||
|
||||
@@ -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 @@ CocoaPodsValidator get cocoapodsValidator => context.get<CocoaPodsValidator>();
|
||||
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 @@ class CocoaPodsValidator extends DoctorValidator {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,22 @@ void main() {
|
||||
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_LIBRARY_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();
|
||||
|
||||
@@ -425,7 +425,7 @@ void main() {
|
||||
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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
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 @@ final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
}, 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ void main() {
|
||||
});
|
||||
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
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 @@ void main() {
|
||||
|
||||
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 @@ void main() {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user