mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

ios-deploy relies on LLDB.framework, which relies on /usr/bin/python and the 'six' module that's installed on the system. However, it appears to use the first version of Python on PATH, rather than explicitly specifying the system install. If a user has a custom install of Python (e.g., via Homebrew or MacPorts) ahead of the system Python on their PATH, LLDB.framework will pick up that version instead. If the user hasn't installed the 'six' module, ios-deploy will fail with a relatively cryptic error message. This patch pushes /usr/bin to the front of PATH for the duration of the ios-deploy run to avoid this scenario. This patch also removes checks for package six. Neither Flutter nor any of its direct dependencies/tooling relies on package six. ios-deploy depends on LLDB.framework (included with Xcode), which relies on a Python script that imports this package but uses whichever Python is at the front of the path. Flutter now invokes ios-deploy with a PATH with /usr/bin forced to the front in order to avoid this problem. We could have retained the check out of paranoia, but this seems unnecessary since it's entirely possible LLDB.framework may one day drop this dependency, in which case I'd expect the base system install of Python would likely drop it as well.
333 lines
13 KiB
Dart
333 lines
13 KiB
Dart
// 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/common.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/cocoapods.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 'package:test/test.dart';
|
|
|
|
import '../src/context.dart';
|
|
|
|
void main() {
|
|
group('iOS Workflow validation', () {
|
|
MockIMobileDevice iMobileDevice;
|
|
MockXcode xcode;
|
|
MockProcessManager processManager;
|
|
MockCocoaPods cocoaPods;
|
|
FileSystem fs;
|
|
|
|
setUp(() {
|
|
iMobileDevice = new MockIMobileDevice();
|
|
xcode = new MockXcode();
|
|
processManager = new MockProcessManager();
|
|
cocoaPods = new MockCocoaPods();
|
|
fs = new MemoryFileSystem();
|
|
|
|
when(cocoaPods.evaluateCocoaPodsInstallation)
|
|
.thenAnswer((_) async => CocoaPodsStatus.recommended);
|
|
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => true);
|
|
when(cocoaPods.cocoaPodsVersionText).thenAnswer((_) async => '1.8.0');
|
|
});
|
|
|
|
testUsingContext('Emit missing status when nothing is installed', () async {
|
|
when(xcode.isInstalled).thenReturn(false);
|
|
when(xcode.xcodeSelectPath).thenReturn(null);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(
|
|
hasHomebrew: false,
|
|
hasIosDeploy: false,
|
|
);
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.missing);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when Xcode is not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(false);
|
|
when(xcode.xcodeSelectPath).thenReturn(null);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when Xcode is partially installed', () async {
|
|
when(xcode.isInstalled).thenReturn(false);
|
|
when(xcode.xcodeSelectPath).thenReturn('/Library/Developer/CommandLineTools');
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when Xcode version too low', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 7.0.1\nBuild version 7C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when Xcode EULA not signed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(false);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when homebrew not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(hasHomebrew: false);
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when libimobiledevice is not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => new MockIMobileDevice(isInstalled: false, isWorking: false),
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when libimobiledevice is installed but not working', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => new MockIMobileDevice(isWorking: false),
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when ios-deploy is not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(hasIosDeploy: false);
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when ios-deploy version is too low', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget(iosDeployVersionText: '1.8.0');
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when CocoaPods is not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(cocoaPods.evaluateCocoaPodsInstallation)
|
|
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when CocoaPods version is too low', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(cocoaPods.evaluateCocoaPodsInstallation)
|
|
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
|
|
final ValidationResult result = await new IOSWorkflowTestTarget().validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
ProcessManager: () => processManager,
|
|
});
|
|
|
|
testUsingContext('Emits partial status when simctl is not installed', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(false);
|
|
final IOSWorkflowTestTarget workflow = new IOSWorkflowTestTarget();
|
|
final ValidationResult result = await workflow.validate();
|
|
expect(result.type, ValidationType.partial);
|
|
}, overrides: <Type, Generator>{
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
});
|
|
|
|
|
|
testUsingContext('Succeeds when all checks pass', () async {
|
|
when(xcode.isInstalled).thenReturn(true);
|
|
when(xcode.versionText)
|
|
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
|
|
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
|
|
when(xcode.eulaSigned).thenReturn(true);
|
|
when(xcode.isSimctlInstalled).thenReturn(true);
|
|
|
|
ensureDirectoryExists(fs.path.join(homeDirPath, '.cocoapods', 'repos', 'master', 'README.md'));
|
|
|
|
final ValidationResult result = await new IOSWorkflowTestTarget().validate();
|
|
expect(result.type, ValidationType.installed);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
IMobileDevice: () => iMobileDevice,
|
|
Xcode: () => xcode,
|
|
CocoaPods: () => cocoaPods,
|
|
ProcessManager: () => processManager,
|
|
});
|
|
});
|
|
}
|
|
|
|
final ProcessResult exitsHappy = new ProcessResult(
|
|
1, // pid
|
|
0, // exitCode
|
|
'', // stdout
|
|
'', // stderr
|
|
);
|
|
|
|
class MockIMobileDevice extends IMobileDevice {
|
|
MockIMobileDevice({
|
|
this.isInstalled = true,
|
|
bool isWorking = true,
|
|
}) : isWorking = new Future<bool>.value(isWorking);
|
|
|
|
@override
|
|
final bool isInstalled;
|
|
|
|
@override
|
|
final Future<bool> isWorking;
|
|
}
|
|
|
|
class MockXcode extends Mock implements Xcode {}
|
|
class MockProcessManager extends Mock implements ProcessManager {}
|
|
class MockCocoaPods extends Mock implements CocoaPods {}
|
|
|
|
class IOSWorkflowTestTarget extends IOSWorkflow {
|
|
IOSWorkflowTestTarget({
|
|
this.hasHomebrew = true,
|
|
bool hasIosDeploy = true,
|
|
String iosDeployVersionText = '1.9.2',
|
|
bool hasIDeviceInstaller = true,
|
|
}) : hasIosDeploy = new Future<bool>.value(hasIosDeploy),
|
|
iosDeployVersionText = new Future<String>.value(iosDeployVersionText),
|
|
hasIDeviceInstaller = new Future<bool>.value(hasIDeviceInstaller);
|
|
|
|
@override
|
|
final bool hasHomebrew;
|
|
|
|
@override
|
|
final Future<bool> hasIosDeploy;
|
|
|
|
@override
|
|
final Future<String> iosDeployVersionText;
|
|
|
|
@override
|
|
final Future<bool> hasIDeviceInstaller;
|
|
}
|