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

re-work the doctor command

parent 1db09ffe
......@@ -102,20 +102,16 @@ class AndroidSdk {
String get adbPath => getPlatformToolsPath('adb');
bool validateSdkWellFormed({ bool complain: false }) {
if (!FileSystemEntity.isFileSync(adbPath)) {
if (complain)
printError('Android SDK file not found: $adbPath.');
return false;
}
/// Validate the Android SDK. This returns an empty list if there are no
/// issues; otherwise, it returns a list of issues found.
List<String> validateSdkWellFormed() {
if (!FileSystemEntity.isFileSync(adbPath))
return <String>['Android SDK file not found: $adbPath.'];
if (sdkVersions.isEmpty) {
if (complain)
printError('Android SDK does not have the proper build-tools.');
return false;
}
if (sdkVersions.isEmpty || latestVersion == null)
return <String>['Android SDK does not have the proper build-tools.'];
return latestVersion.validateSdkWellFormed(complain: complain);
return latestVersion.validateSdkWellFormed();
}
String getPlatformToolsPath(String binaryName) {
......@@ -215,12 +211,20 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
String get zipalignPath => getBuildToolsPath('zipalign');
bool validateSdkWellFormed({ bool complain: false }) {
return
_exists(androidJarPath, complain: complain) &&
_exists(aaptPath, complain: complain) &&
_exists(dxPath, complain: complain) &&
_exists(zipalignPath, complain: complain);
List<String> validateSdkWellFormed() {
if (_exists(androidJarPath) != null)
return <String>[_exists(androidJarPath)];
if (_exists(aaptPath) != null)
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) {
......@@ -237,13 +241,9 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
@override
String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]';
bool _exists(String path, { bool complain: false }) {
if (!FileSystemEntity.isFileSync(path)) {
if (complain)
printError('Android SDK file not found: $path.');
return false;
}
return true;
String _exists(String path) {
if (!FileSystemEntity.isFileSync(path))
return 'Android SDK file not found: $path.';
return null;
}
}
......@@ -6,9 +6,8 @@ import '../doctor.dart';
import '../globals.dart';
import 'android_sdk.dart';
class AndroidWorkflow extends Workflow {
@override
String get label => 'Android toolchain';
class AndroidWorkflow extends DoctorValidator implements Workflow {
AndroidWorkflow() : super('Android toolchain - develop for Android devices');
@override
bool get appliesToHostPlatform => true;
......@@ -17,30 +16,43 @@ class AndroidWorkflow extends Workflow {
bool get canListDevices => getAdbPath(androidSdk) != null;
@override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed(complain: false);
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
@override
ValidationResult validate() {
Validator androidValidator = new Validator(
label,
description: 'develop for Android devices'
);
ValidationType sdkExists() {
return androidSdk == null ? ValidationType.missing : ValidationType.installed;
};
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
List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing;
String sdkVersionText;
if (androidSdk == null) {
messages.add(new ValidationMessage.error(
'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).'
));
} 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
void diagnose() => validate().print();
List<String> validationResult = androidSdk.validateSdkWellFormed();
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 {
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.');
return 1;
}
......@@ -375,7 +377,9 @@ Future<int> buildAndroid({
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.');
return 1;
}
......
......@@ -4,10 +4,8 @@
import 'dart:async';
import '../artifacts.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import '../runner/version.dart';
class DoctorCommand extends FlutterCommand {
@override
......@@ -21,18 +19,8 @@ class DoctorCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
// general info
String flutterRoot = ArtifactStore.flutterRoot;
printStatus('Flutter root: $flutterRoot.');
printStatus('');
// doctor
doctor.diagnose();
printStatus('');
// version
printStatus(getVersion(flutterRoot));
return 0;
}
}
......@@ -22,7 +22,7 @@ class UpgradeCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
try {
runCheckedSync(<String>[
......@@ -50,7 +50,7 @@ class UpgradeCommand extends FlutterCommand {
return code;
printStatus('');
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
return 0;
}
......
This diff is collapsed.
......@@ -79,7 +79,7 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
if (!doctor.iosWorkflow.hasIdeviceId)
if (!doctor.iosWorkflow.hasIDeviceId)
return <IOSDevice>[];
List<IOSDevice> devices = [];
......
......@@ -8,112 +8,96 @@ import '../base/process.dart';
import '../doctor.dart';
import 'mac.dart';
class IOSWorkflow extends Workflow {
@override
String get label => 'iOS toolchain';
XCode get xcode => XCode.instance;
class IOSWorkflow extends DoctorValidator implements Workflow {
IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
@override
bool get appliesToHostPlatform => Platform.isMacOS;
// We need xcode (+simctl) to list simulator devices, and idevice_id to list real devices.
@override
bool get canListDevices => XCode.instance.isInstalledAndMeetsVersionCheck;
bool get canListDevices => xcode.isInstalledAndMeetsVersionCheck;
// We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy
// for real devices.
@override
bool get canLaunchDevices => XCode.instance.isInstalledAndMeetsVersionCheck;
bool get canLaunchDevices => xcode.isInstalledAndMeetsVersionCheck;
bool get hasIDeviceId => exitsHappy(<String>['idevice_id', '-h']);
@override
ValidationResult validate() {
Validator iosValidator = new Validator(
label,
description: 'develop for iOS devices'
);
List<ValidationMessage> messages = <ValidationMessage>[];
int installCount = 0;
String xcodeVersionInfo;
ValidationType xcodeExists() {
return XCode.instance.isInstalled ? ValidationType.installed : ValidationType.missing;
};
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
);
if (xcode.isInstalled) {
installCount++;
iosValidator.addValidator(xcodeValidator);
xcodeVersionInfo = xcode.xcodeVersionText;
if (xcodeVersionInfo.contains(','))
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
xcodeValidator.addValidator(new Validator(
'version',
description: 'Xcode minimum version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0',
resolution: 'Download the latest version or update via the Mac App Store',
validatorFunction: xcodeVersionSatisfactory
messages.add(new ValidationMessage(xcode.xcodeVersionText));
if (!xcode.isInstalledAndMeetsVersionCheck) {
messages.add(new ValidationMessage.error(
'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(
'EULA',
description: 'XCode end user license agreement',
resolution: "Open XCode or run the command 'sudo xcodebuild -license'",
validatorFunction: xcodeEulaSigned
if (!xcode.eulaSigned) {
messages.add(new ValidationMessage.error(
'XCode end user license agreement not signed; open XCode or run the command \'sudo xcodebuild -license\'.'
));
}
} 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',
description: 'install additional development packages',
resolution: 'Download at http://brew.sh/',
validatorFunction: brewExists
);
// brew installed
if (exitsHappy(<String>['brew', '-v'])) {
installCount++;
iosValidator.addValidator(brewValidator);
List<String> installed = <String>[];
brewValidator.addValidator(new Validator(
'ideviceinstaller',
description: 'discover connected iOS devices',
resolution: "Install via 'brew install ideviceinstaller'",
validatorFunction: ideviceinstallerExists
if (!exitsHappy(<String>['ideviceinstaller', '-h'])) {
messages.add(new ValidationMessage.error(
'ideviceinstaller not available; this is used to discover connected iOS devices.\n'
'Install via \'brew install ideviceinstaller\'.'
));
} else {
installed.add('ideviceinstaller');
}
brewValidator.addValidator(new Validator(
'ios-deploy',
description: 'deploy to connected iOS devices',
resolution: "Install via 'brew install ios-deploy'",
validatorFunction: iosdeployExists
if (!hasIDeviceId) {
messages.add(new ValidationMessage.error(
'ios-deploy not available; this is used to deploy to connected iOS devices.\n'
'Install via \'brew install ios-deploy\'.'
));
return iosValidator.validate();
} else {
installed.add('ios-deploy');
}
@override
void diagnose() => validate().print();
bool get hasIdeviceId => exitsHappy(<String>['idevice_id', '-h']);
if (installed.isNotEmpty)
messages.add(new ValidationMessage(installed.join(', ') + ' installed'));
} else {
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
/// simulator) is installed on the user's machine.
bool get canWorkWithIOSDevices {
return exitsHappy(<String>['ideviceinstaller', '-h']) && hasIdeviceId;
return new ValidationResult(
installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing,
messages,
statusInfo: xcodeVersionInfo
);
}
}
......@@ -54,27 +54,24 @@ class XCode {
}
}
bool _xcodeVersionSatisfactory;
bool get xcodeVersionSatisfactory {
if (_xcodeVersionSatisfactory != null)
return _xcodeVersionSatisfactory;
String _xcodeVersionText;
try {
String output = runSync(<String>['xcodebuild', '-version']);
String get xcodeVersionText {
if (_xcodeVersionText == null)
_xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', ');
return _xcodeVersionText;
}
bool get xcodeVersionSatisfactory {
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('.');
int major = int.parse(components[0]);
int minor = components.length == 1 ? 0 : int.parse(components[1]);
_xcodeVersionSatisfactory = _xcodeVersionCheckValid(major, minor);
} catch (error) {
_xcodeVersionSatisfactory = false;
}
return _xcodeVersionSatisfactory;
return _xcodeVersionCheckValid(major, minor);
}
}
......
......@@ -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']) {
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus('');
doctor.summary();
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
return new Future<int>.value(0);
}
......
......@@ -5,26 +5,54 @@
import '../artifacts.dart';
import '../base/process.dart';
String getVersion(String flutterRoot) {
String upstream = runSync([
'git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'
], workingDirectory: flutterRoot).trim();
String repository;
int slash = upstream.indexOf('/');
class FlutterVersion {
FlutterVersion(this.flutterRoot) {
_channel = _runGit('git rev-parse --abbrev-ref --symbolic @{u}');
int slash = _channel.indexOf('/');
if (slash != -1) {
String remote = upstream.substring(0, slash);
repository = runSync([
'git', 'ls-remote', '--get-url', remote
], workingDirectory: flutterRoot).trim();
upstream = upstream.substring(slash + 1);
String remote = _channel.substring(0, slash);
_repositoryUrl = _runGit('git ls-remote --get-url $remote');
_channel = _channel.substring(slash + 1);
} else if (_channel.isEmpty) {
_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)';
String flutterVersion = 'Framework: $revision';
String engineRevision = 'Engine: ${ArtifactStore.engineRevision}';
final String flutterRoot;
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