mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Implement macOS support in flutter doctor
(#33277)
Splits Xcode validation out of the iOS validator and into a stand-alone validator, and groups the CocoaPods validator with that top-level validator instead of the iOS validator. iOS now validates only the iOS-specific tools (e.g., ideviceinstaller). Reorganizes many of the associated clases so that those that are used by both macOS and iOS live in macos/ rather than ios/. Moves some validators to their own files as part of the restructuring. This is the macOS portion of #31368
This commit is contained in:
parent
156b4220b4
commit
81c38b22cb
@ -14,7 +14,7 @@ import '../cache.dart';
|
||||
import '../compile.dart';
|
||||
import '../dart/package_map.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/mac.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../project.dart';
|
||||
import 'context.dart';
|
||||
import 'file_system.dart';
|
||||
|
@ -126,24 +126,26 @@ class UserMessages {
|
||||
'Android Studio not found; download from https://developer.android.com/studio/index.html\n'
|
||||
'(or visit https://flutter.dev/setup/#android-setup for detailed instructions).';
|
||||
|
||||
// Messages used in IOSValidator
|
||||
String iOSXcodeLocation(String location) => 'Xcode at $location';
|
||||
String iOSXcodeOutdated(int versionMajor, int versionMinor) =>
|
||||
// Messages used in XcodeValidator
|
||||
String xcodeLocation(String location) => 'Xcode at $location';
|
||||
String xcodeOutdated(int versionMajor, int versionMinor) =>
|
||||
'Flutter requires a minimum Xcode version of $versionMajor.$versionMinor.0.\n'
|
||||
'Download the latest version or update via the Mac App Store.';
|
||||
String get iOSXcodeEula => 'Xcode end user license agreement not signed; open Xcode or run the command \'sudo xcodebuild -license\'.';
|
||||
String get iOSXcodeMissingSimct =>
|
||||
String get xcodeEula => 'Xcode end user license agreement not signed; open Xcode or run the command \'sudo xcodebuild -license\'.';
|
||||
String get xcodeMissingSimct =>
|
||||
'Xcode requires additional components to be installed in order to run.\n'
|
||||
'Launch Xcode and install additional required components when prompted.';
|
||||
String get iOSXcodeMissing =>
|
||||
String get xcodeMissing =>
|
||||
'Xcode not installed; this is necessary for iOS development.\n'
|
||||
'Download at https://developer.apple.com/xcode/download/.';
|
||||
String get iOSXcodeIncomplete =>
|
||||
String get xcodeIncomplete =>
|
||||
'Xcode installation is incomplete; a full installation is necessary for iOS development.\n'
|
||||
'Download at: https://developer.apple.com/xcode/download/\n'
|
||||
'Or install Xcode via the App Store.\n'
|
||||
'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'
|
||||
@ -204,6 +206,9 @@ 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';
|
||||
|
@ -12,7 +12,7 @@ import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/mac.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../project.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../run_cold.dart';
|
||||
|
@ -30,13 +30,16 @@ import 'emulator.dart';
|
||||
import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
|
||||
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaSdk, FuchsiaArtifacts;
|
||||
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow;
|
||||
import 'ios/cocoapods.dart';
|
||||
import 'ios/ios_workflow.dart';
|
||||
import 'ios/mac.dart';
|
||||
import 'ios/simulators.dart';
|
||||
import 'ios/xcodeproj.dart';
|
||||
import 'linux/linux_workflow.dart';
|
||||
import 'macos/cocoapods.dart';
|
||||
import 'macos/cocoapods_validator.dart';
|
||||
import 'macos/macos_workflow.dart';
|
||||
import 'macos/xcode.dart';
|
||||
import 'macos/xcode_validator.dart';
|
||||
import 'run_hot.dart';
|
||||
import 'usage.dart';
|
||||
import 'version.dart';
|
||||
@ -99,6 +102,7 @@ Future<T> runInContext<T>(
|
||||
WebCompiler: () => const WebCompiler(),
|
||||
WindowsWorkflow: () => const WindowsWorkflow(),
|
||||
Xcode: () => Xcode(),
|
||||
XcodeValidator: () => const XcodeValidator(),
|
||||
XcodeProjectInterpreter: () => XcodeProjectInterpreter(),
|
||||
},
|
||||
);
|
||||
|
@ -26,7 +26,9 @@ import 'intellij/intellij.dart';
|
||||
import 'ios/ios_workflow.dart';
|
||||
import 'ios/plist_utils.dart';
|
||||
import 'linux/linux_workflow.dart';
|
||||
import 'macos/cocoapods_validator.dart';
|
||||
import 'macos/macos_workflow.dart';
|
||||
import 'macos/xcode_validator.dart';
|
||||
import 'proxy_validator.dart';
|
||||
import 'tester/flutter_tester.dart';
|
||||
import 'version.dart';
|
||||
@ -58,8 +60,11 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
if (androidWorkflow.appliesToHostPlatform)
|
||||
_validators.add(GroupedValidator(<DoctorValidator>[androidValidator, androidLicenseValidator]));
|
||||
|
||||
if (iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
|
||||
_validators.add(GroupedValidator(<DoctorValidator>[xcodeValidator, cocoapodsValidator]));
|
||||
|
||||
if (iosWorkflow.appliesToHostPlatform)
|
||||
_validators.add(GroupedValidator(<DoctorValidator>[iosValidator, cocoapodsValidator]));
|
||||
_validators.add(iosValidator);
|
||||
|
||||
final List<DoctorValidator> ideValidators = <DoctorValidator>[];
|
||||
ideValidators.addAll(AndroidStudioValidator.allValidators);
|
||||
|
@ -8,7 +8,7 @@ import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../emulator.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/mac.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import 'ios_workflow.dart';
|
||||
|
||||
class IOSEmulators extends EmulatorDiscovery {
|
||||
|
@ -11,13 +11,12 @@ import '../base/process.dart';
|
||||
import '../base/user_messages.dart';
|
||||
import '../base/version.dart';
|
||||
import '../doctor.dart';
|
||||
import 'cocoapods.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>();
|
||||
CocoaPodsValidator get cocoapodsValidator => context.get<CocoaPodsValidator>();
|
||||
|
||||
class IOSWorkflow implements Workflow {
|
||||
const IOSWorkflow();
|
||||
@ -44,7 +43,7 @@ class IOSWorkflow implements Workflow {
|
||||
|
||||
class IOSValidator extends DoctorValidator {
|
||||
|
||||
const IOSValidator() : super('iOS toolchain - develop for iOS devices');
|
||||
const IOSValidator() : super('iOS tools - develop for iOS devices');
|
||||
|
||||
Future<bool> get hasIDeviceInstaller => exitsHappyAsync(<String>['ideviceinstaller', '-h']);
|
||||
|
||||
@ -79,44 +78,7 @@ class IOSValidator extends DoctorValidator {
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
ValidationType xcodeStatus = ValidationType.missing;
|
||||
ValidationType packageManagerStatus = ValidationType.installed;
|
||||
String xcodeVersionInfo;
|
||||
|
||||
if (xcode.isInstalled) {
|
||||
xcodeStatus = ValidationType.installed;
|
||||
|
||||
messages.add(ValidationMessage(userMessages.iOSXcodeLocation(xcode.xcodeSelectPath)));
|
||||
|
||||
xcodeVersionInfo = xcode.versionText;
|
||||
if (xcodeVersionInfo.contains(','))
|
||||
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
|
||||
messages.add(ValidationMessage(xcode.versionText));
|
||||
|
||||
if (!xcode.isInstalledAndMeetsVersionCheck) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(
|
||||
userMessages.iOSXcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor)
|
||||
));
|
||||
}
|
||||
|
||||
if (!xcode.eulaSigned) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.iOSXcodeEula));
|
||||
}
|
||||
if (!xcode.isSimctlInstalled) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.iOSXcodeMissingSimct));
|
||||
}
|
||||
|
||||
} else {
|
||||
xcodeStatus = ValidationType.missing;
|
||||
if (xcode.xcodeSelectPath == null || xcode.xcodeSelectPath.isEmpty) {
|
||||
messages.add(ValidationMessage.error(userMessages.iOSXcodeMissing));
|
||||
} else {
|
||||
messages.add(ValidationMessage.error(userMessages.iOSXcodeIncomplete));
|
||||
}
|
||||
}
|
||||
|
||||
int checksFailed = 0;
|
||||
|
||||
@ -155,61 +117,9 @@ class IOSValidator extends DoctorValidator {
|
||||
if (checksFailed == totalChecks)
|
||||
packageManagerStatus = ValidationType.missing;
|
||||
if (checksFailed > 0 && !hasHomebrew) {
|
||||
messages.add(ValidationMessage.error(userMessages.iOSBrewMissing));
|
||||
messages.add(ValidationMessage.hint(userMessages.iOSBrewMissing));
|
||||
}
|
||||
|
||||
return ValidationResult(
|
||||
<ValidationType>[xcodeStatus, packageManagerStatus].reduce(_mergeValidationTypes),
|
||||
messages,
|
||||
statusInfo: xcodeVersionInfo,
|
||||
);
|
||||
}
|
||||
|
||||
ValidationType _mergeValidationTypes(ValidationType t1, ValidationType t2) {
|
||||
return t1 == t2 ? t1 : ValidationType.partial;
|
||||
}
|
||||
}
|
||||
|
||||
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>[];
|
||||
|
||||
ValidationType status = ValidationType.installed;
|
||||
if (hasHomebrew) {
|
||||
final CocoaPodsStatus cocoaPodsStatus = await cocoaPods
|
||||
.evaluateCocoaPodsInstallation;
|
||||
|
||||
if (cocoaPodsStatus == CocoaPodsStatus.recommended) {
|
||||
if (await cocoaPods.isCocoaPodsInitialized) {
|
||||
messages.add(ValidationMessage(userMessages.cocoaPodsVersion(await cocoaPods.cocoaPodsVersionText)));
|
||||
} else {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.cocoaPodsUninitialized(noCocoaPodsConsequence)));
|
||||
}
|
||||
} else {
|
||||
if (cocoaPodsStatus == CocoaPodsStatus.notInstalled) {
|
||||
status = ValidationType.missing;
|
||||
messages.add(ValidationMessage.error(
|
||||
userMessages.cocoaPodsMissing(noCocoaPodsConsequence, cocoaPodsInstallInstructions)));
|
||||
} else if (cocoaPodsStatus == CocoaPodsStatus.unknownVersion) {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.hint(
|
||||
userMessages.cocoaPodsUnknownVersion(unknownCocoaPodsConsequence, cocoaPodsUpgradeInstructions)));
|
||||
} else {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.hint(
|
||||
userMessages.cocoaPodsOutdated(cocoaPods.cocoaPodsRecommendedVersion, noCocoaPodsConsequence, cocoaPodsUpgradeInstructions)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only set status. The main validator handles messages for missing brew.
|
||||
status = ValidationType.missing;
|
||||
}
|
||||
return ValidationResult(status, messages);
|
||||
return ValidationResult(packageManagerStatus, messages);
|
||||
}
|
||||
}
|
||||
|
@ -21,19 +21,16 @@ import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart';
|
||||
import '../macos/cocoapods.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import '../services.dart';
|
||||
import 'cocoapods.dart';
|
||||
import 'code_signing.dart';
|
||||
import 'xcodeproj.dart';
|
||||
|
||||
const int kXcodeRequiredVersionMajor = 9;
|
||||
const int kXcodeRequiredVersionMinor = 0;
|
||||
|
||||
IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
|
||||
PlistBuddy get plistBuddy => context.get<PlistBuddy>();
|
||||
Xcode get xcode => context.get<Xcode>();
|
||||
|
||||
class PlistBuddy {
|
||||
const PlistBuddy();
|
||||
@ -154,100 +151,6 @@ class IMobileDevice {
|
||||
}
|
||||
}
|
||||
|
||||
class Xcode {
|
||||
bool get isInstalledAndMeetsVersionCheck => isInstalled && isVersionSatisfactory;
|
||||
|
||||
String _xcodeSelectPath;
|
||||
String get xcodeSelectPath {
|
||||
if (_xcodeSelectPath == null) {
|
||||
try {
|
||||
_xcodeSelectPath = processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']).stdout.trim();
|
||||
} on ProcessException {
|
||||
// Ignored, return null below.
|
||||
}
|
||||
}
|
||||
return _xcodeSelectPath;
|
||||
}
|
||||
|
||||
bool get isInstalled {
|
||||
if (xcodeSelectPath == null || xcodeSelectPath.isEmpty)
|
||||
return false;
|
||||
return xcodeProjectInterpreter.isInstalled;
|
||||
}
|
||||
|
||||
int get majorVersion => xcodeProjectInterpreter.majorVersion;
|
||||
|
||||
int get minorVersion => xcodeProjectInterpreter.minorVersion;
|
||||
|
||||
String get versionText => xcodeProjectInterpreter.versionText;
|
||||
|
||||
bool _eulaSigned;
|
||||
/// Has the EULA been signed?
|
||||
bool get eulaSigned {
|
||||
if (_eulaSigned == null) {
|
||||
try {
|
||||
final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']);
|
||||
if (result.stdout != null && result.stdout.contains('license'))
|
||||
_eulaSigned = false;
|
||||
else if (result.stderr != null && result.stderr.contains('license'))
|
||||
_eulaSigned = false;
|
||||
else
|
||||
_eulaSigned = true;
|
||||
} on ProcessException {
|
||||
_eulaSigned = false;
|
||||
}
|
||||
}
|
||||
return _eulaSigned;
|
||||
}
|
||||
|
||||
bool _isSimctlInstalled;
|
||||
|
||||
/// Verifies that simctl is installed by trying to run it.
|
||||
bool get isSimctlInstalled {
|
||||
if (_isSimctlInstalled == null) {
|
||||
try {
|
||||
// This command will error if additional components need to be installed in
|
||||
// xcode 9.2 and above.
|
||||
final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'simctl', 'list']);
|
||||
_isSimctlInstalled = result.stderr == null || result.stderr == '';
|
||||
} on ProcessException {
|
||||
_isSimctlInstalled = false;
|
||||
}
|
||||
}
|
||||
return _isSimctlInstalled;
|
||||
}
|
||||
|
||||
bool get isVersionSatisfactory {
|
||||
if (!xcodeProjectInterpreter.isInstalled)
|
||||
return false;
|
||||
if (majorVersion > kXcodeRequiredVersionMajor)
|
||||
return true;
|
||||
if (majorVersion == kXcodeRequiredVersionMajor)
|
||||
return minorVersion >= kXcodeRequiredVersionMinor;
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<RunResult> cc(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'cc']..addAll(args));
|
||||
}
|
||||
|
||||
Future<RunResult> clang(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'clang']..addAll(args));
|
||||
}
|
||||
|
||||
String getSimulatorPath() {
|
||||
if (xcodeSelectPath == null)
|
||||
return null;
|
||||
final List<String> searchPaths = <String>[
|
||||
fs.path.join(xcodeSelectPath, 'Applications', 'Simulator.app'),
|
||||
];
|
||||
return searchPaths.where((String p) => p != null).firstWhere(
|
||||
(String p) => fs.directory(p).existsSync(),
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the Xcode system.
|
||||
///
|
||||
/// Xcode 10 added a new (default) build system with better performance and
|
||||
|
@ -19,6 +19,7 @@ import '../bundle.dart' as bundle;
|
||||
import '../convert.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../project.dart';
|
||||
import '../protocol_discovery.dart';
|
||||
import 'ios_workflow.dart';
|
||||
|
@ -17,12 +17,12 @@ import '../base/process_manager.dart';
|
||||
import '../base/version.dart';
|
||||
import '../cache.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/xcodeproj.dart';
|
||||
import '../project.dart';
|
||||
import 'xcodeproj.dart';
|
||||
|
||||
const String noCocoaPodsConsequence = '''
|
||||
CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
|
||||
Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
|
||||
CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side.
|
||||
Without CocoaPods, plugins will not work on iOS or macOS.
|
||||
For more info, see https://flutter.dev/platform-plugins''';
|
||||
|
||||
const String unknownCocoaPodsConsequence = '''
|
@ -0,0 +1,58 @@
|
||||
// Copyright 2016 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 '../base/context.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/user_messages.dart';
|
||||
import '../doctor.dart';
|
||||
import 'cocoapods.dart';
|
||||
|
||||
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>[];
|
||||
|
||||
final CocoaPodsStatus cocoaPodsStatus = await cocoaPods
|
||||
.evaluateCocoaPodsInstallation;
|
||||
|
||||
ValidationType status = ValidationType.installed;
|
||||
if (cocoaPodsStatus == CocoaPodsStatus.recommended) {
|
||||
if (await cocoaPods.isCocoaPodsInitialized) {
|
||||
messages.add(ValidationMessage(userMessages.cocoaPodsVersion(await cocoaPods.cocoaPodsVersionText)));
|
||||
} else {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.cocoaPodsUninitialized(noCocoaPodsConsequence)));
|
||||
}
|
||||
} else {
|
||||
if (cocoaPodsStatus == CocoaPodsStatus.notInstalled) {
|
||||
status = ValidationType.missing;
|
||||
messages.add(ValidationMessage.error(
|
||||
userMessages.cocoaPodsMissing(noCocoaPodsConsequence, cocoaPodsInstallInstructions)));
|
||||
} else if (cocoaPodsStatus == CocoaPodsStatus.unknownVersion) {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.hint(
|
||||
userMessages.cocoaPodsUnknownVersion(unknownCocoaPodsConsequence, cocoaPodsUpgradeInstructions)));
|
||||
} else {
|
||||
status = ValidationType.partial;
|
||||
messages.add(ValidationMessage.hint(
|
||||
userMessages.cocoaPodsOutdated(cocoaPods.cocoaPodsRecommendedVersion, noCocoaPodsConsequence, cocoaPodsUpgradeInstructions)));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
111
packages/flutter_tools/lib/src/macos/xcode.dart
Normal file
111
packages/flutter_tools/lib/src/macos/xcode.dart
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2016 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 '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/process_manager.dart';
|
||||
import '../ios/xcodeproj.dart';
|
||||
|
||||
const int kXcodeRequiredVersionMajor = 9;
|
||||
const int kXcodeRequiredVersionMinor = 0;
|
||||
|
||||
Xcode get xcode => context.get<Xcode>();
|
||||
|
||||
class Xcode {
|
||||
bool get isInstalledAndMeetsVersionCheck => isInstalled && isVersionSatisfactory;
|
||||
|
||||
String _xcodeSelectPath;
|
||||
String get xcodeSelectPath {
|
||||
if (_xcodeSelectPath == null) {
|
||||
try {
|
||||
_xcodeSelectPath = processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']).stdout.trim();
|
||||
} on ProcessException {
|
||||
// Ignored, return null below.
|
||||
}
|
||||
}
|
||||
return _xcodeSelectPath;
|
||||
}
|
||||
|
||||
bool get isInstalled {
|
||||
if (xcodeSelectPath == null || xcodeSelectPath.isEmpty)
|
||||
return false;
|
||||
return xcodeProjectInterpreter.isInstalled;
|
||||
}
|
||||
|
||||
int get majorVersion => xcodeProjectInterpreter.majorVersion;
|
||||
|
||||
int get minorVersion => xcodeProjectInterpreter.minorVersion;
|
||||
|
||||
String get versionText => xcodeProjectInterpreter.versionText;
|
||||
|
||||
bool _eulaSigned;
|
||||
/// Has the EULA been signed?
|
||||
bool get eulaSigned {
|
||||
if (_eulaSigned == null) {
|
||||
try {
|
||||
final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']);
|
||||
if (result.stdout != null && result.stdout.contains('license'))
|
||||
_eulaSigned = false;
|
||||
else if (result.stderr != null && result.stderr.contains('license'))
|
||||
_eulaSigned = false;
|
||||
else
|
||||
_eulaSigned = true;
|
||||
} on ProcessException {
|
||||
_eulaSigned = false;
|
||||
}
|
||||
}
|
||||
return _eulaSigned;
|
||||
}
|
||||
|
||||
bool _isSimctlInstalled;
|
||||
|
||||
/// Verifies that simctl is installed by trying to run it.
|
||||
bool get isSimctlInstalled {
|
||||
if (_isSimctlInstalled == null) {
|
||||
try {
|
||||
// This command will error if additional components need to be installed in
|
||||
// xcode 9.2 and above.
|
||||
final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'simctl', 'list']);
|
||||
_isSimctlInstalled = result.stderr == null || result.stderr == '';
|
||||
} on ProcessException {
|
||||
_isSimctlInstalled = false;
|
||||
}
|
||||
}
|
||||
return _isSimctlInstalled;
|
||||
}
|
||||
|
||||
bool get isVersionSatisfactory {
|
||||
if (!xcodeProjectInterpreter.isInstalled)
|
||||
return false;
|
||||
if (majorVersion > kXcodeRequiredVersionMajor)
|
||||
return true;
|
||||
if (majorVersion == kXcodeRequiredVersionMajor)
|
||||
return minorVersion >= kXcodeRequiredVersionMinor;
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<RunResult> cc(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'cc']..addAll(args));
|
||||
}
|
||||
|
||||
Future<RunResult> clang(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'clang']..addAll(args));
|
||||
}
|
||||
|
||||
String getSimulatorPath() {
|
||||
if (xcodeSelectPath == null)
|
||||
return null;
|
||||
final List<String> searchPaths = <String>[
|
||||
fs.path.join(xcodeSelectPath, 'Applications', 'Simulator.app'),
|
||||
];
|
||||
return searchPaths.where((String p) => p != null).firstWhere(
|
||||
(String p) => fs.directory(p).existsSync(),
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
}
|
58
packages/flutter_tools/lib/src/macos/xcode_validator.dart
Normal file
58
packages/flutter_tools/lib/src/macos/xcode_validator.dart
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2016 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 '../base/context.dart';
|
||||
import '../base/user_messages.dart';
|
||||
import '../doctor.dart';
|
||||
import 'xcode.dart';
|
||||
|
||||
XcodeValidator get xcodeValidator => context.get<XcodeValidator>();
|
||||
|
||||
class XcodeValidator extends DoctorValidator {
|
||||
const XcodeValidator() : super('Xcode - develop for iOS and macOS');
|
||||
|
||||
@override
|
||||
Future<ValidationResult> validate() async {
|
||||
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||
ValidationType xcodeStatus = ValidationType.missing;
|
||||
String xcodeVersionInfo;
|
||||
|
||||
if (xcode.isInstalled) {
|
||||
xcodeStatus = ValidationType.installed;
|
||||
|
||||
messages.add(ValidationMessage(userMessages.xcodeLocation(xcode.xcodeSelectPath)));
|
||||
|
||||
xcodeVersionInfo = xcode.versionText;
|
||||
if (xcodeVersionInfo.contains(','))
|
||||
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
|
||||
messages.add(ValidationMessage(xcode.versionText));
|
||||
|
||||
if (!xcode.isInstalledAndMeetsVersionCheck) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(
|
||||
userMessages.xcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor)
|
||||
));
|
||||
}
|
||||
|
||||
if (!xcode.eulaSigned) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.xcodeEula));
|
||||
}
|
||||
if (!xcode.isSimctlInstalled) {
|
||||
xcodeStatus = ValidationType.partial;
|
||||
messages.add(ValidationMessage.error(userMessages.xcodeMissingSimct));
|
||||
}
|
||||
|
||||
} else {
|
||||
xcodeStatus = ValidationType.missing;
|
||||
if (xcode.xcodeSelectPath == null || xcode.xcodeSelectPath.isEmpty) {
|
||||
messages.add(ValidationMessage.error(userMessages.xcodeMissing));
|
||||
} else {
|
||||
messages.add(ValidationMessage.error(userMessages.xcodeIncomplete));
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult(xcodeStatus, messages, statusInfo: xcodeVersionInfo);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ import 'package:yaml/yaml.dart';
|
||||
import 'base/file_system.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'globals.dart';
|
||||
import 'ios/cocoapods.dart';
|
||||
import 'macos/cocoapods.dart';
|
||||
import 'project.dart';
|
||||
|
||||
void _renderTemplateToFile(String template, dynamic context, String filePath) {
|
||||
|
@ -14,7 +14,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/version.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
|
@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/config.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/emulator.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_emulators.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
|
@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/ios/devices.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
@ -5,11 +5,9 @@
|
||||
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';
|
||||
@ -22,28 +20,17 @@ void main() {
|
||||
group('iOS Workflow validation', () {
|
||||
MockIMobileDevice iMobileDevice;
|
||||
MockIMobileDevice iMobileDeviceUninstalled;
|
||||
MockXcode xcode;
|
||||
MockProcessManager processManager;
|
||||
MockCocoaPods cocoaPods;
|
||||
FileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
iMobileDevice = MockIMobileDevice();
|
||||
iMobileDeviceUninstalled = MockIMobileDevice(isInstalled: false);
|
||||
xcode = MockXcode();
|
||||
processManager = MockProcessManager();
|
||||
cocoaPods = MockCocoaPods();
|
||||
fs = 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 = IOSWorkflowTestTarget(
|
||||
hasHomebrew: false,
|
||||
hasIosDeploy: false,
|
||||
@ -54,121 +41,33 @@ void main() {
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
IMobileDevice: () => iMobileDeviceUninstalled,
|
||||
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 = 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 = 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 = 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 = IOSWorkflowTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
IMobileDevice: () => iMobileDevice,
|
||||
Xcode: () => xcode,
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits installed status when homebrew not installed, but not needed', () 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 = IOSWorkflowTestTarget(hasHomebrew: false);
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.installed);
|
||||
}, 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 = IOSWorkflowTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
IMobileDevice: () => 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 = IOSWorkflowTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
IMobileDevice: () => MockIMobileDevice(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);
|
||||
when(processManager.run(
|
||||
<String>['ideviceinfo', '-u', '00008020-001C2D903C42002E'],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
@ -194,163 +93,42 @@ Show information about a connected device.
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
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 = 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 = 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 ios-deploy version is a known bad version', () 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 = IOSWorkflowTestTarget(iosDeployVersionText: '2.0.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 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 = 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 IOSWorkflowTestTarget().validate();
|
||||
expect(result.type, ValidationType.installed);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
IMobileDevice: () => iMobileDevice,
|
||||
Xcode: () => xcode,
|
||||
CocoaPods: () => cocoaPods,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
});
|
||||
|
||||
group('iOS CocoaPods validation', () {
|
||||
MockCocoaPods cocoaPods;
|
||||
|
||||
setUp(() {
|
||||
cocoaPods = MockCocoaPods();
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.recommended);
|
||||
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => true);
|
||||
when(cocoaPods.cocoaPodsVersionText).thenAnswer((_) async => '1.8.0');
|
||||
});
|
||||
|
||||
testUsingContext('Emits installed status when CocoaPods is installed', () async {
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.installed);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits missing status when CocoaPods is not installed', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods is installed with unknown version', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.unknownVersion);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
|
||||
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods version is too low', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits missing status when homebrew is not installed', () async {
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget(hasHomebrew: false);
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
final ProcessResult exitsHappy = ProcessResult(
|
||||
@ -373,9 +151,7 @@ class MockIMobileDevice extends IMobileDevice {
|
||||
final Future<bool> isWorking;
|
||||
}
|
||||
|
||||
class MockXcode extends Mock implements Xcode {}
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockCocoaPods extends Mock implements CocoaPods {}
|
||||
class MockProcessResult extends Mock implements ProcessResult {}
|
||||
|
||||
class IOSWorkflowTestTarget extends IOSValidator {
|
||||
@ -400,12 +176,3 @@ class IOSWorkflowTestTarget extends IOSValidator {
|
||||
@override
|
||||
final Future<bool> hasIDeviceInstaller;
|
||||
}
|
||||
|
||||
class CocoaPodsTestTarget extends CocoaPodsValidator {
|
||||
CocoaPodsTestTarget({
|
||||
this.hasHomebrew = true,
|
||||
});
|
||||
|
||||
@override
|
||||
final bool hasHomebrew;
|
||||
}
|
||||
|
@ -178,102 +178,6 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('Xcode', () {
|
||||
MockProcessManager mockProcessManager;
|
||||
Xcode xcode;
|
||||
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
||||
xcode = Xcode();
|
||||
});
|
||||
|
||||
testUsingContext('xcodeSelectPath returns null when xcode-select is not installed', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||
.thenThrow(const ProcessException('/usr/bin/xcode-select', <String>['--print-path']));
|
||||
expect(xcode.xcodeSelectPath, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeSelectPath returns path when xcode-select is installed', () {
|
||||
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
|
||||
expect(xcode.xcodeSelectPath, xcodePath);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(8);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(17);
|
||||
expect(xcode.isVersionSatisfactory, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
|
||||
expect(xcode.isVersionSatisfactory, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when version meets minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(1);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is false when clang is not installed', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenThrow(const ProcessException('/usr/bin/xcrun', <String>['clang']));
|
||||
expect(xcode.eulaSigned, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenReturn(ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
|
||||
expect(xcode.eulaSigned, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenReturn(ProcessResult(1, 1, '', 'clang: error: no input files'));
|
||||
expect(xcode.eulaSigned, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
|
||||
group('Diagnose Xcode build failure', () {
|
||||
Map<String, String> buildSettings;
|
||||
|
||||
|
@ -14,6 +14,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/ios/simulators.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
@ -10,10 +10,10 @@ import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
||||
import 'package:flutter_tools/src/plugins.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/ios/cocoapods.dart';
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
@ -0,0 +1,91 @@
|
||||
// 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 'package:flutter_tools/src/doctor.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
||||
import 'package:flutter_tools/src/macos/cocoapods_validator.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
void main() {
|
||||
group('CocoaPods validation', () {
|
||||
MockCocoaPods cocoaPods;
|
||||
|
||||
setUp(() {
|
||||
cocoaPods = MockCocoaPods();
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.recommended);
|
||||
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => true);
|
||||
when(cocoaPods.cocoaPodsVersionText).thenAnswer((_) async => '1.8.0');
|
||||
});
|
||||
|
||||
testUsingContext('Emits installed status when CocoaPods is installed', () async {
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.installed);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits missing status when CocoaPods is not installed', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.notInstalled);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods is installed with unknown version', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.unknownVersion);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
|
||||
when(cocoaPods.isCocoaPodsInitialized).thenAnswer((_) async => false);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
final ValidationResult result = await workflow.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
CocoaPods: () => cocoaPods,
|
||||
});
|
||||
|
||||
testUsingContext('Emits partial status when CocoaPods version is too low', () async {
|
||||
when(cocoaPods.evaluateCocoaPodsInstallation)
|
||||
.thenAnswer((_) async => CocoaPodsStatus.belowRecommendedVersion);
|
||||
final CocoaPodsTestTarget workflow = CocoaPodsTestTarget();
|
||||
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;
|
||||
}
|
113
packages/flutter_tools/test/macos/xcode_test.dart
Normal file
113
packages/flutter_tools/test/macos/xcode_test.dart
Normal file
@ -0,0 +1,113 @@
|
||||
// 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 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
|
||||
|
||||
void main() {
|
||||
group('Xcode', () {
|
||||
MockProcessManager mockProcessManager;
|
||||
Xcode xcode;
|
||||
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
|
||||
xcode = Xcode();
|
||||
});
|
||||
|
||||
testUsingContext('xcodeSelectPath returns null when xcode-select is not installed', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||
.thenThrow(const ProcessException('/usr/bin/xcode-select', <String>['--print-path']));
|
||||
expect(xcode.xcodeSelectPath, isNull);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeSelectPath returns path when xcode-select is installed', () {
|
||||
const String xcodePath = '/Applications/Xcode8.0.app/Contents/Developer';
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']))
|
||||
.thenReturn(ProcessResult(1, 0, xcodePath, ''));
|
||||
expect(xcode.xcodeSelectPath, xcodePath);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(8);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(17);
|
||||
expect(xcode.isVersionSatisfactory, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
|
||||
expect(xcode.isVersionSatisfactory, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when version meets minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
|
||||
when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
|
||||
when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
|
||||
when(mockXcodeProjectInterpreter.minorVersion).thenReturn(1);
|
||||
expect(xcode.isVersionSatisfactory, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is false when clang is not installed', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenThrow(const ProcessException('/usr/bin/xcrun', <String>['clang']));
|
||||
expect(xcode.eulaSigned, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is false when clang output indicates EULA not yet accepted', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenReturn(ProcessResult(1, 1, '', 'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.'));
|
||||
expect(xcode.eulaSigned, isFalse);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('eulaSigned is true when clang output indicates EULA has been accepted', () {
|
||||
when(mockProcessManager.runSync(<String>['/usr/bin/xcrun', 'clang']))
|
||||
.thenReturn(ProcessResult(1, 1, '', 'clang: error: no input files'));
|
||||
expect(xcode.eulaSigned, isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
}
|
105
packages/flutter_tools/test/macos/xcode_validator_test.dart
Normal file
105
packages/flutter_tools/test/macos/xcode_validator_test.dart
Normal file
@ -0,0 +1,105 @@
|
||||
// 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 'package:flutter_tools/src/doctor.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode_validator.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockXcode extends Mock implements Xcode {}
|
||||
|
||||
void main() {
|
||||
group('Xcode validation', () {
|
||||
MockXcode xcode;
|
||||
MockProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
xcode = MockXcode();
|
||||
processManager = MockProcessManager();
|
||||
});
|
||||
|
||||
testUsingContext('Emits missing status when Xcode is not installed', () async {
|
||||
when(xcode.isInstalled).thenReturn(false);
|
||||
when(xcode.xcodeSelectPath).thenReturn(null);
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
});
|
||||
|
||||
testUsingContext('Emits missing status when Xcode installation is incomplete', () async {
|
||||
when(xcode.isInstalled).thenReturn(false);
|
||||
when(xcode.xcodeSelectPath).thenReturn('/Library/Developer/CommandLineTools');
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.missing);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
});
|
||||
|
||||
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);
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
});
|
||||
|
||||
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);
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
});
|
||||
|
||||
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);
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.partial);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
});
|
||||
|
||||
|
||||
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);
|
||||
const XcodeValidator validator = XcodeValidator();
|
||||
final ValidationResult result = await validator.validate();
|
||||
expect(result.type, ValidationType.installed);
|
||||
}, overrides: <Type, Generator>{
|
||||
Xcode: () => xcode,
|
||||
ProcessManager: () => processManager,
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user