Commit 25f332d8 authored by Devon Carew's avatar Devon Carew

re-work the doctor command

parent 1db09ffe
...@@ -102,20 +102,16 @@ class AndroidSdk { ...@@ -102,20 +102,16 @@ class AndroidSdk {
String get adbPath => getPlatformToolsPath('adb'); String get adbPath => getPlatformToolsPath('adb');
bool validateSdkWellFormed({ bool complain: false }) { /// Validate the Android SDK. This returns an empty list if there are no
if (!FileSystemEntity.isFileSync(adbPath)) { /// issues; otherwise, it returns a list of issues found.
if (complain) List<String> validateSdkWellFormed() {
printError('Android SDK file not found: $adbPath.'); if (!FileSystemEntity.isFileSync(adbPath))
return false; return <String>['Android SDK file not found: $adbPath.'];
}
if (sdkVersions.isEmpty) { if (sdkVersions.isEmpty || latestVersion == null)
if (complain) return <String>['Android SDK does not have the proper build-tools.'];
printError('Android SDK does not have the proper build-tools.');
return false;
}
return latestVersion.validateSdkWellFormed(complain: complain); return latestVersion.validateSdkWellFormed();
} }
String getPlatformToolsPath(String binaryName) { String getPlatformToolsPath(String binaryName) {
...@@ -215,12 +211,20 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> { ...@@ -215,12 +211,20 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
String get zipalignPath => getBuildToolsPath('zipalign'); String get zipalignPath => getBuildToolsPath('zipalign');
bool validateSdkWellFormed({ bool complain: false }) { List<String> validateSdkWellFormed() {
return if (_exists(androidJarPath) != null)
_exists(androidJarPath, complain: complain) && return <String>[_exists(androidJarPath)];
_exists(aaptPath, complain: complain) &&
_exists(dxPath, complain: complain) && if (_exists(aaptPath) != null)
_exists(zipalignPath, complain: complain); return <String>[_exists(aaptPath)];
if (_exists(dxPath) != null)
return <String>[_exists(dxPath)];
if (_exists(zipalignPath) != null)
return <String>[_exists(zipalignPath)];
return <String>[];
} }
String getPlatformsPath(String itemName) { String getPlatformsPath(String itemName) {
...@@ -237,13 +241,9 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> { ...@@ -237,13 +241,9 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
@override @override
String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]'; String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]';
bool _exists(String path, { bool complain: false }) { String _exists(String path) {
if (!FileSystemEntity.isFileSync(path)) { if (!FileSystemEntity.isFileSync(path))
if (complain) return 'Android SDK file not found: $path.';
printError('Android SDK file not found: $path.'); return null;
return false;
}
return true;
} }
} }
...@@ -6,9 +6,8 @@ import '../doctor.dart'; ...@@ -6,9 +6,8 @@ import '../doctor.dart';
import '../globals.dart'; import '../globals.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
class AndroidWorkflow extends Workflow { class AndroidWorkflow extends DoctorValidator implements Workflow {
@override AndroidWorkflow() : super('Android toolchain - develop for Android devices');
String get label => 'Android toolchain';
@override @override
bool get appliesToHostPlatform => true; bool get appliesToHostPlatform => true;
...@@ -17,30 +16,43 @@ class AndroidWorkflow extends Workflow { ...@@ -17,30 +16,43 @@ class AndroidWorkflow extends Workflow {
bool get canListDevices => getAdbPath(androidSdk) != null; bool get canListDevices => getAdbPath(androidSdk) != null;
@override @override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed(complain: false); bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
@override @override
ValidationResult validate() { ValidationResult validate() {
Validator androidValidator = new Validator( List<ValidationMessage> messages = <ValidationMessage>[];
label, ValidationType type = ValidationType.missing;
description: 'develop for Android devices' String sdkVersionText;
);
if (androidSdk == null) {
ValidationType sdkExists() { messages.add(new ValidationMessage.error(
return androidSdk == null ? ValidationType.missing : ValidationType.installed; 'Android Studio / Android SDK not found. Download from https://developer.android.com/sdk/\n'
}; '(or visit https://flutter.io/setup/#android-setup for detailed instructions).'
androidValidator.addValidator(new Validator(
'Android Studio / Android SDK',
description: 'enable development for Android devices',
resolution: 'Download from https://developer.android.com/sdk/ (or visit '
'https://flutter.io/setup/#android-setup for detailed instructions)',
validatorFunction: sdkExists
)); ));
} else {
type = ValidationType.partial;
messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
if (androidSdk.latestVersion != null) {
sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
return androidValidator.validate(); messages.add(new ValidationMessage('Platform ${androidSdk.latestVersion.platformVersionName}'));
messages.add(new ValidationMessage('Build-tools ${androidSdk.latestVersion.buildToolsVersionName}'));
} }
@override List<String> validationResult = androidSdk.validateSdkWellFormed();
void diagnose() => validate().print();
if (validationResult.isEmpty) {
type = ValidationType.installed;
} else {
messages.addAll(validationResult.map((String message) {
return new ValidationMessage.error(message);
}));
messages.add(new ValidationMessage('Try re-installing or updating your Android SDK.'));
}
}
return new ValidationResult(type, messages, statusInfo: sdkVersionText);
}
} }
...@@ -178,7 +178,9 @@ class BuildApkCommand extends FlutterCommand { ...@@ -178,7 +178,9 @@ class BuildApkCommand extends FlutterCommand {
return 1; return 1;
} }
if (!androidSdk.validateSdkWellFormed(complain: true)) { List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.'); printError('Try re-installing or updating your Android SDK.');
return 1; return 1;
} }
...@@ -375,7 +377,9 @@ Future<int> buildAndroid({ ...@@ -375,7 +377,9 @@ Future<int> buildAndroid({
return 1; return 1;
} }
if (!androidSdk.validateSdkWellFormed(complain: true)) { List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.'); printError('Try re-installing or updating your Android SDK.');
return 1; return 1;
} }
......
...@@ -4,10 +4,8 @@ ...@@ -4,10 +4,8 @@
import 'dart:async'; import 'dart:async';
import '../artifacts.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../runner/version.dart';
class DoctorCommand extends FlutterCommand { class DoctorCommand extends FlutterCommand {
@override @override
...@@ -21,18 +19,8 @@ class DoctorCommand extends FlutterCommand { ...@@ -21,18 +19,8 @@ class DoctorCommand extends FlutterCommand {
@override @override
Future<int> runInProject() async { Future<int> runInProject() async {
// general info
String flutterRoot = ArtifactStore.flutterRoot;
printStatus('Flutter root: $flutterRoot.');
printStatus('');
// doctor // doctor
doctor.diagnose(); doctor.diagnose();
printStatus('');
// version
printStatus(getVersion(flutterRoot));
return 0; return 0;
} }
} }
...@@ -22,7 +22,7 @@ class UpgradeCommand extends FlutterCommand { ...@@ -22,7 +22,7 @@ class UpgradeCommand extends FlutterCommand {
@override @override
Future<int> runInProject() async { Future<int> runInProject() async {
printStatus(getVersion(ArtifactStore.flutterRoot)); printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
try { try {
runCheckedSync(<String>[ runCheckedSync(<String>[
...@@ -50,7 +50,7 @@ class UpgradeCommand extends FlutterCommand { ...@@ -50,7 +50,7 @@ class UpgradeCommand extends FlutterCommand {
return code; return code;
printStatus(''); printStatus('');
printStatus(getVersion(ArtifactStore.flutterRoot)); printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
return 0; return 0;
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert' show JSON;
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
...@@ -11,19 +12,20 @@ import 'base/context.dart'; ...@@ -11,19 +12,20 @@ import 'base/context.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'globals.dart'; import 'globals.dart';
import 'ios/ios_workflow.dart'; import 'ios/ios_workflow.dart';
import 'runner/version.dart';
// TODO(devoncarew): Make it easy to add version information to the `doctor` printout.
class Doctor { class Doctor {
Doctor() { Doctor() {
_iosWorkflow = new IOSWorkflow(); _validators.add(new _FlutterValidator());
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
_androidWorkflow = new AndroidWorkflow(); _androidWorkflow = new AndroidWorkflow();
if (_androidWorkflow.appliesToHostPlatform) if (_androidWorkflow.appliesToHostPlatform)
_validators.add(_androidWorkflow); _validators.add(_androidWorkflow);
_iosWorkflow = new IOSWorkflow();
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
_validators.add(new _AtomValidator()); _validators.add(new _AtomValidator());
} }
...@@ -55,13 +57,19 @@ class Doctor { ...@@ -55,13 +57,19 @@ class Doctor {
for (DoctorValidator validator in _validators) { for (DoctorValidator validator in _validators) {
ValidationResult result = validator.validate(); ValidationResult result = validator.validate();
buffer.write('${result.leadingBox} The ${validator.label} is '); buffer.write('${result.leadingBox} ${validator.title} is ');
if (result.type == ValidationType.missing) if (result.type == ValidationType.missing)
buffer.writeln('not installed.'); buffer.write('not installed.');
else if (result.type == ValidationType.partial) else if (result.type == ValidationType.partial)
buffer.writeln('partially installed; more components are available.'); buffer.write('partially installed; more components are available.');
else else
buffer.writeln('fully installed.'); buffer.write('fully installed.');
if (result.statusInfo != null)
buffer.write(' (${result.statusInfo})');
buffer.writeln();
if (result.type != ValidationType.installed) if (result.type != ValidationType.installed)
allGood = false; allGood = false;
} }
...@@ -77,11 +85,26 @@ class Doctor { ...@@ -77,11 +85,26 @@ class Doctor {
/// Print verbose information about the state of installed tooling. /// Print verbose information about the state of installed tooling.
void diagnose() { void diagnose() {
bool firstLine = true; bool firstLine = true;
for (DoctorValidator validator in _validators) { for (DoctorValidator validator in _validators) {
if (!firstLine) if (!firstLine)
printStatus(''); printStatus('');
firstLine = false; firstLine = false;
validator.diagnose();
ValidationResult result = validator.validate();
if (result.statusInfo != null)
printStatus('${result.leadingBox} ${validator.title} (${result.statusInfo})');
else
printStatus('${result.leadingBox} ${validator.title}');
for (ValidationMessage message in result.messages) {
if (message.isError) {
printStatus(' x ${message.message.replaceAll('\n', '\n ')}');
} else {
printStatus(' • ${message.message.replaceAll('\n', '\n ')}');
}
}
} }
} }
...@@ -90,17 +113,8 @@ class Doctor { ...@@ -90,17 +113,8 @@ class Doctor {
bool get canLaunchAnything => workflows.any((Workflow workflow) => workflow.canLaunchDevices); bool get canLaunchAnything => workflows.any((Workflow workflow) => workflow.canLaunchDevices);
} }
abstract class DoctorValidator {
String get label;
ValidationResult validate();
/// Print verbose information about the state of the workflow.
void diagnose();
}
/// A series of tools and required install steps for a target platform (iOS or Android). /// A series of tools and required install steps for a target platform (iOS or Android).
abstract class Workflow extends DoctorValidator { abstract class Workflow {
/// Whether the workflow applies to this platform (as in, should we ever try and use it). /// Whether the workflow applies to this platform (as in, should we ever try and use it).
bool get appliesToHostPlatform; bool get appliesToHostPlatform;
...@@ -117,57 +131,21 @@ enum ValidationType { ...@@ -117,57 +131,21 @@ enum ValidationType {
installed installed
} }
typedef ValidationType ValidationFunction(); abstract class DoctorValidator {
DoctorValidator(this.title);
class Validator {
Validator(this.name, { this.description, this.resolution, this.validatorFunction });
final String name;
final String description;
final String resolution;
final ValidationFunction validatorFunction;
List<Validator> _children = <Validator>[];
ValidationResult validate() {
List<ValidationResult> childResults;
ValidationType type;
if (validatorFunction != null)
type = validatorFunction();
childResults = _children.map((Validator child) => child.validate()).toList();
// If there's no immediate validator, the result we return is synthesized
// from the sub-tree of children. This is so we can show that the branch is
// not fully installed.
if (type == null) {
type = _combine(childResults
.expand((ValidationResult child) => child._allResults)
.map((ValidationResult result) => result.type)
);
}
return new ValidationResult(type, this, childResults);
}
ValidationType _combine(Iterable<ValidationType> types) { final String title;
if (types.contains(ValidationType.missing) && types.contains(ValidationType.installed))
return ValidationType.partial;
if (types.contains(ValidationType.missing))
return ValidationType.missing;
return ValidationType.installed;
}
void addValidator(Validator validator) => _children.add(validator); ValidationResult validate();
} }
class ValidationResult { class ValidationResult {
ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]); ValidationResult(this.type, this.messages, { this.statusInfo });
final ValidationType type; final ValidationType type;
final Validator validator; // A short message about the status.
final List<ValidationResult> childResults; final String statusInfo;
final List<ValidationMessage> messages;
String get leadingBox { String get leadingBox {
if (type == ValidationType.missing) if (type == ValidationType.missing)
...@@ -177,49 +155,42 @@ class ValidationResult { ...@@ -177,49 +155,42 @@ class ValidationResult {
else else
return '[-]'; return '[-]';
} }
}
void print([String indent = '']) { class ValidationMessage {
printSelf(indent); ValidationMessage(this.message) : isError = false;
ValidationMessage.error(this.message) : isError = true;
for (ValidationResult child in childResults)
child.print(indent + ' ');
}
void printSelf([String indent = '']) {
String result = indent;
if (type == ValidationType.missing) final bool isError;
result += '$leadingBox '; final String message;
else if (type == ValidationType.installed)
result += '$leadingBox ';
else
result += '$leadingBox ';
result += '${validator.name} '; @override
String toString() => message;
}
if (validator.description != null) class _FlutterValidator extends DoctorValidator {
result += '- ${validator.description} '; _FlutterValidator() : super('Flutter');
if (type == ValidationType.missing) @override
result += '(missing)'; ValidationResult validate() {
else if (type == ValidationType.installed) List<ValidationMessage> messages = <ValidationMessage>[];
result += '(installed)';
printStatus(result); FlutterVersion version = FlutterVersion.getVersion();
if (type == ValidationType.missing && validator.resolution != null) messages.add(new ValidationMessage('Flutter root at ${version.flutterRoot}'));
printStatus('$indent ${validator.resolution}'); messages.add(new ValidationMessage('Framework revision ${version.frameworkRevisionShort} '
} '(${version.frameworkAge})'));
messages.add(new ValidationMessage('Engine revision ${version.engineRevisionShort}'));
List<ValidationResult> get _allResults { return new ValidationResult(ValidationType.installed, messages,
List<ValidationResult> results = <ValidationResult>[this]; statusInfo: '${version.frameworkRevisionShort} - channel ${version.channel}');
results.addAll(childResults);
return results;
} }
} }
class _AtomValidator extends DoctorValidator { class _AtomValidator extends DoctorValidator {
static String getAtomHomePath() { _AtomValidator() : super('Atom - a lightweight development environment for Flutter');
static String _getAtomHomePath() {
final Map<String, String> env = Platform.environment; final Map<String, String> env = Platform.environment;
if (env['ATOM_HOME'] != null) if (env['ATOM_HOME'] != null)
return env['ATOM_HOME']; return env['ATOM_HOME'];
...@@ -228,43 +199,43 @@ class _AtomValidator extends DoctorValidator { ...@@ -228,43 +199,43 @@ class _AtomValidator extends DoctorValidator {
: path.join(env['HOME'], '.atom'); : path.join(env['HOME'], '.atom');
} }
@override
String get label => 'Atom development environment';
@override @override
ValidationResult validate() { ValidationResult validate() {
Validator atomValidator = new Validator( List<ValidationMessage> messages = <ValidationMessage>[];
label,
description: 'a lightweight development environment for Flutter'
);
ValidationType atomExists() { int installCount = 0;
bool atomDirExists = FileSystemEntity.isDirectorySync(getAtomHomePath());
return atomDirExists ? ValidationType.installed : ValidationType.missing; bool atomDirExists = FileSystemEntity.isDirectorySync(_getAtomHomePath());
}; if (!atomDirExists) {
messages.add(new ValidationMessage.error(
ValidationType flutterPluginExists() { 'Atom not installed; download at https://atom.io.'
String flutterPluginPath = path.join(getAtomHomePath(), 'packages', 'flutter');
bool flutterPluginExists = FileSystemEntity.isDirectorySync(flutterPluginPath);
return flutterPluginExists ? ValidationType.installed : ValidationType.missing;
};
atomValidator.addValidator(new Validator(
'Atom editor',
resolution: 'Download at https://atom.io',
validatorFunction: atomExists
)); ));
} else {
installCount++;
}
atomValidator.addValidator(new Validator( String flutterPluginPath = path.join(_getAtomHomePath(), 'packages', 'flutter');
'Flutter plugin', if (!FileSystemEntity.isDirectorySync(flutterPluginPath)) {
description: 'adds Flutter specific functionality to Atom', messages.add(new ValidationMessage.error(
resolution: "Install the 'flutter' plugin in Atom or run 'apm install flutter'", 'Flutter plugin not installed; this adds Flutter specific functionality to Atom.\n'
validatorFunction: flutterPluginExists 'Install the \'flutter\' plugin in Atom or run \'apm install flutter\'.'
)); ));
} else {
installCount++;
return atomValidator.validate(); try {
File packageFile = new File(path.join(flutterPluginPath, 'package.json'));
dynamic packageInfo = JSON.decode(packageFile.readAsStringSync());
String version = packageInfo['version'];
messages.add(new ValidationMessage('Atom installed; flutter plugin version $version'));
} catch (error) {
printTrace('Unable to read flutter plugin version: $error');
}
} }
@override return new ValidationResult(
void diagnose() => validate().print(); installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing,
messages
);
}
} }
...@@ -79,7 +79,7 @@ class IOSDevice extends Device { ...@@ -79,7 +79,7 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false; bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) { static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
if (!doctor.iosWorkflow.hasIdeviceId) if (!doctor.iosWorkflow.hasIDeviceId)
return <IOSDevice>[]; return <IOSDevice>[];
List<IOSDevice> devices = []; List<IOSDevice> devices = [];
......
...@@ -8,112 +8,96 @@ import '../base/process.dart'; ...@@ -8,112 +8,96 @@ import '../base/process.dart';
import '../doctor.dart'; import '../doctor.dart';
import 'mac.dart'; import 'mac.dart';
class IOSWorkflow extends Workflow { XCode get xcode => XCode.instance;
@override
String get label => 'iOS toolchain'; class IOSWorkflow extends DoctorValidator implements Workflow {
IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
@override @override
bool get appliesToHostPlatform => Platform.isMacOS; bool get appliesToHostPlatform => Platform.isMacOS;
// We need xcode (+simctl) to list simulator devices, and idevice_id to list real devices. // We need xcode (+simctl) to list simulator devices, and idevice_id to list real devices.
@override @override
bool get canListDevices => XCode.instance.isInstalledAndMeetsVersionCheck; bool get canListDevices => xcode.isInstalledAndMeetsVersionCheck;
// We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy // We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy
// for real devices. // for real devices.
@override @override
bool get canLaunchDevices => XCode.instance.isInstalledAndMeetsVersionCheck; bool get canLaunchDevices => xcode.isInstalledAndMeetsVersionCheck;
bool get hasIDeviceId => exitsHappy(<String>['idevice_id', '-h']);
@override @override
ValidationResult validate() { ValidationResult validate() {
Validator iosValidator = new Validator( List<ValidationMessage> messages = <ValidationMessage>[];
label, int installCount = 0;
description: 'develop for iOS devices' String xcodeVersionInfo;
);
ValidationType xcodeExists() { if (xcode.isInstalled) {
return XCode.instance.isInstalled ? ValidationType.installed : ValidationType.missing; installCount++;
};
ValidationType xcodeVersionSatisfactory() {
return XCode.instance.isInstalledAndMeetsVersionCheck ? ValidationType.installed : ValidationType.missing;
};
ValidationType xcodeEulaSigned() {
return XCode.instance.eulaSigned ? ValidationType.installed : ValidationType.missing;
};
ValidationType brewExists() {
return exitsHappy(<String>['brew', '-v'])
? ValidationType.installed : ValidationType.missing;
};
ValidationType ideviceinstallerExists() {
return exitsHappy(<String>['ideviceinstaller', '-h'])
? ValidationType.installed : ValidationType.missing;
};
ValidationType iosdeployExists() {
return hasIdeviceId ? ValidationType.installed : ValidationType.missing;
};
Validator xcodeValidator = new Validator(
'XCode',
description: 'enable development for iOS devices',
resolution: 'Download at https://developer.apple.com/xcode/download/',
validatorFunction: xcodeExists
);
iosValidator.addValidator(xcodeValidator); xcodeVersionInfo = xcode.xcodeVersionText;
if (xcodeVersionInfo.contains(','))
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
xcodeValidator.addValidator(new Validator( messages.add(new ValidationMessage(xcode.xcodeVersionText));
'version',
description: 'Xcode minimum version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0', if (!xcode.isInstalledAndMeetsVersionCheck) {
resolution: 'Download the latest version or update via the Mac App Store', messages.add(new ValidationMessage.error(
validatorFunction: xcodeVersionSatisfactory 'Flutter requires a minimum XCode version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0.\n'
'Download the latest version or update via the Mac App Store.'
)); ));
}
xcodeValidator.addValidator(new Validator( if (!xcode.eulaSigned) {
'EULA', messages.add(new ValidationMessage.error(
description: 'XCode end user license agreement', 'XCode end user license agreement not signed; open XCode or run the command \'sudo xcodebuild -license\'.'
resolution: "Open XCode or run the command 'sudo xcodebuild -license'", ));
validatorFunction: xcodeEulaSigned }
} else {
messages.add(new ValidationMessage.error(
'XCode not installed; this is necessary for iOS development.\n'
'Download at https://developer.apple.com/xcode/download/.'
)); ));
}
Validator brewValidator = new Validator( // brew installed
'brew', if (exitsHappy(<String>['brew', '-v'])) {
description: 'install additional development packages', installCount++;
resolution: 'Download at http://brew.sh/',
validatorFunction: brewExists
);
iosValidator.addValidator(brewValidator); List<String> installed = <String>[];
brewValidator.addValidator(new Validator( if (!exitsHappy(<String>['ideviceinstaller', '-h'])) {
'ideviceinstaller', messages.add(new ValidationMessage.error(
description: 'discover connected iOS devices', 'ideviceinstaller not available; this is used to discover connected iOS devices.\n'
resolution: "Install via 'brew install ideviceinstaller'", 'Install via \'brew install ideviceinstaller\'.'
validatorFunction: ideviceinstallerExists
)); ));
} else {
installed.add('ideviceinstaller');
}
brewValidator.addValidator(new Validator( if (!hasIDeviceId) {
'ios-deploy', messages.add(new ValidationMessage.error(
description: 'deploy to connected iOS devices', 'ios-deploy not available; this is used to deploy to connected iOS devices.\n'
resolution: "Install via 'brew install ios-deploy'", 'Install via \'brew install ios-deploy\'.'
validatorFunction: iosdeployExists
)); ));
} else {
return iosValidator.validate(); installed.add('ios-deploy');
} }
@override if (installed.isNotEmpty)
void diagnose() => validate().print(); messages.add(new ValidationMessage(installed.join(', ') + ' installed'));
} else {
bool get hasIdeviceId => exitsHappy(<String>['idevice_id', '-h']); messages.add(new ValidationMessage.error(
'Brew not installed; use this to install tools for iOS device development.\n'
'Download brew at http://brew.sh/.'
));
}
/// Return whether the tooling to list and deploy to real iOS devices (not the return new ValidationResult(
/// simulator) is installed on the user's machine. installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing,
bool get canWorkWithIOSDevices { messages,
return exitsHappy(<String>['ideviceinstaller', '-h']) && hasIdeviceId; statusInfo: xcodeVersionInfo
);
} }
} }
...@@ -54,27 +54,24 @@ class XCode { ...@@ -54,27 +54,24 @@ class XCode {
} }
} }
bool _xcodeVersionSatisfactory; String _xcodeVersionText;
bool get xcodeVersionSatisfactory {
if (_xcodeVersionSatisfactory != null)
return _xcodeVersionSatisfactory;
try { String get xcodeVersionText {
String output = runSync(<String>['xcodebuild', '-version']); if (_xcodeVersionText == null)
_xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', ');
return _xcodeVersionText;
}
bool get xcodeVersionSatisfactory {
RegExp regex = new RegExp(r'Xcode ([0-9.]+)'); RegExp regex = new RegExp(r'Xcode ([0-9.]+)');
String version = regex.firstMatch(output).group(1); String version = regex.firstMatch(xcodeVersionText).group(1);
List<String> components = version.split('.'); List<String> components = version.split('.');
int major = int.parse(components[0]); int major = int.parse(components[0]);
int minor = components.length == 1 ? 0 : int.parse(components[1]); int minor = components.length == 1 ? 0 : int.parse(components[1]);
_xcodeVersionSatisfactory = _xcodeVersionCheckValid(major, minor); return _xcodeVersionCheckValid(major, minor);
} catch (error) {
_xcodeVersionSatisfactory = false;
}
return _xcodeVersionSatisfactory;
} }
} }
......
...@@ -200,16 +200,8 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -200,16 +200,8 @@ class FlutterCommandRunner extends CommandRunner {
} }
} }
if (androidSdk != null) {
printTrace('Using Android SDK at ${androidSdk.directory}.');
if (androidSdk.latestVersion != null)
printTrace('${androidSdk.latestVersion}');
}
if (globalResults['version']) { if (globalResults['version']) {
printStatus(getVersion(ArtifactStore.flutterRoot)); printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
printStatus('');
doctor.summary();
return new Future<int>.value(0); return new Future<int>.value(0);
} }
......
...@@ -5,26 +5,54 @@ ...@@ -5,26 +5,54 @@
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/process.dart'; import '../base/process.dart';
String getVersion(String flutterRoot) { class FlutterVersion {
String upstream = runSync([ FlutterVersion(this.flutterRoot) {
'git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}' _channel = _runGit('git rev-parse --abbrev-ref --symbolic @{u}');
], workingDirectory: flutterRoot).trim();
String repository; int slash = _channel.indexOf('/');
int slash = upstream.indexOf('/');
if (slash != -1) { if (slash != -1) {
String remote = upstream.substring(0, slash); String remote = _channel.substring(0, slash);
repository = runSync([ _repositoryUrl = _runGit('git ls-remote --get-url $remote');
'git', 'ls-remote', '--get-url', remote _channel = _channel.substring(slash + 1);
], workingDirectory: flutterRoot).trim(); } else if (_channel.isEmpty) {
upstream = upstream.substring(slash + 1); _channel = 'unknown';
}
_frameworkRevision = _runGit('git log -n 1 --pretty=format:%H');
_frameworkAge = _runGit('git log -n 1 --pretty=format:%ar');
} }
String revision = runSync([
'git', 'log', '-n', '1', '--pretty=format:%H (%ar)'
], workingDirectory: flutterRoot).trim();
String from = repository == null ? 'Flutter from unknown source' : 'Flutter from $repository (on $upstream)'; final String flutterRoot;
String flutterVersion = 'Framework: $revision';
String engineRevision = 'Engine: ${ArtifactStore.engineRevision}'; String _repositoryUrl;
String get repositoryUrl => _repositoryUrl;
String _channel;
/// `master`, `alpha`, `hackathon`, ...
String get channel => _channel;
String _frameworkRevision;
String get frameworkRevision => _frameworkRevision;
String get frameworkRevisionShort => _runGit('git rev-parse --short $frameworkRevision');
String _frameworkAge;
String get frameworkAge => _frameworkAge;
return '$from\n$flutterVersion\n$engineRevision'; String get engineRevision => ArtifactStore.engineRevision;
String get engineRevisionShort => _runGit('git rev-parse --short $engineRevision');
String _runGit(String command) => runSync(command.split(' '), workingDirectory: flutterRoot);
@override
String toString() {
String from = repositoryUrl == null ? 'Flutter from unknown source' : 'Flutter from $repositoryUrl (on channel $channel)';
String flutterText = 'Framework: $frameworkRevisionShort ($frameworkAge)';
String engineText = 'Engine: $engineRevisionShort';
return '$from\n\n$flutterText\n$engineText';
}
static FlutterVersion getVersion([String flutterRoot]) {
return new FlutterVersion(flutterRoot != null ? flutterRoot : ArtifactStore.flutterRoot);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment