re-work the doctor command
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart index 6788135..c21676b 100644 --- a/packages/flutter_tools/lib/src/android/android_sdk.dart +++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -102,20 +102,16 @@ 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 @@ 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 @@ @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; } }
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index 9bccb0a..e2a65e7 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -6,9 +6,8 @@ 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 @@ 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' - ); + List<ValidationMessage> messages = <ValidationMessage>[]; + ValidationType type = ValidationType.missing; + String sdkVersionText; - ValidationType sdkExists() { - return androidSdk == null ? ValidationType.missing : ValidationType.installed; - }; + 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; - 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 - )); + messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}')); - return androidValidator.validate(); + if (androidSdk.latestVersion != null) { + sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}'; + + messages.add(new ValidationMessage('Platform ${androidSdk.latestVersion.platformVersionName}')); + messages.add(new ValidationMessage('Build-tools ${androidSdk.latestVersion.buildToolsVersionName}')); + } + + 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); } - - @override - void diagnose() => validate().print(); }
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index b1e8f0f..6c4319c9 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -178,7 +178,9 @@ 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 @@ 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; }
diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart index 188024d..be5d886 100644 --- a/packages/flutter_tools/lib/src/commands/doctor.dart +++ b/packages/flutter_tools/lib/src/commands/doctor.dart
@@ -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 @@ @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; } }
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index a4b327f..76fab60 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -22,7 +22,7 @@ @override Future<int> runInProject() async { - printStatus(getVersion(ArtifactStore.flutterRoot)); + printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString()); try { runCheckedSync(<String>[ @@ -50,7 +50,7 @@ return code; printStatus(''); - printStatus(getVersion(ArtifactStore.flutterRoot)); + printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString()); return 0; }
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 41c75cb..2eda8a0 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert' show JSON; import 'dart:io'; import 'package:path/path.dart' as path; @@ -11,19 +12,20 @@ import 'base/os.dart'; import 'globals.dart'; import 'ios/ios_workflow.dart'; - -// TODO(devoncarew): Make it easy to add version information to the `doctor` printout. +import 'runner/version.dart'; class Doctor { Doctor() { - _iosWorkflow = new IOSWorkflow(); - if (_iosWorkflow.appliesToHostPlatform) - _validators.add(_iosWorkflow); + _validators.add(new _FlutterValidator()); _androidWorkflow = new AndroidWorkflow(); if (_androidWorkflow.appliesToHostPlatform) _validators.add(_androidWorkflow); + _iosWorkflow = new IOSWorkflow(); + if (_iosWorkflow.appliesToHostPlatform) + _validators.add(_iosWorkflow); + _validators.add(new _AtomValidator()); } @@ -55,13 +57,19 @@ for (DoctorValidator validator in _validators) { ValidationResult result = validator.validate(); - buffer.write('${result.leadingBox} The ${validator.label} is '); + buffer.write('${result.leadingBox} ${validator.title} is '); if (result.type == ValidationType.missing) - buffer.writeln('not installed.'); + buffer.write('not installed.'); else if (result.type == ValidationType.partial) - buffer.writeln('partially installed; more components are available.'); + buffer.write('partially installed; more components are available.'); else - buffer.writeln('fully installed.'); + buffer.write('fully installed.'); + + if (result.statusInfo != null) + buffer.write(' (${result.statusInfo})'); + + buffer.writeln(); + if (result.type != ValidationType.installed) allGood = false; } @@ -77,11 +85,26 @@ /// Print verbose information about the state of installed tooling. void diagnose() { bool firstLine = true; + for (DoctorValidator validator in _validators) { if (!firstLine) printStatus(''); 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 @@ 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). -abstract class Workflow extends DoctorValidator { +abstract class Workflow { /// Whether the workflow applies to this platform (as in, should we ever try and use it). bool get appliesToHostPlatform; @@ -117,57 +131,21 @@ installed } -typedef ValidationType ValidationFunction(); +abstract class DoctorValidator { + DoctorValidator(this.title); -class Validator { - Validator(this.name, { this.description, this.resolution, this.validatorFunction }); + final String title; - 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) { - 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 { - ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]); + ValidationResult(this.type, this.messages, { this.statusInfo }); final ValidationType type; - final Validator validator; - final List<ValidationResult> childResults; + // A short message about the status. + final String statusInfo; + final List<ValidationMessage> messages; String get leadingBox { if (type == ValidationType.missing) @@ -177,49 +155,42 @@ else return '[-]'; } +} - void print([String indent = '']) { - printSelf(indent); +class ValidationMessage { + ValidationMessage(this.message) : isError = false; + ValidationMessage.error(this.message) : isError = true; - for (ValidationResult child in childResults) - child.print(indent + ' '); - } + final bool isError; + final String message; - void printSelf([String indent = '']) { - String result = indent; + @override + String toString() => message; +} - if (type == ValidationType.missing) - result += '$leadingBox '; - else if (type == ValidationType.installed) - result += '$leadingBox '; - else - result += '$leadingBox '; +class _FlutterValidator extends DoctorValidator { + _FlutterValidator() : super('Flutter'); - result += '${validator.name} '; + @override + ValidationResult validate() { + List<ValidationMessage> messages = <ValidationMessage>[]; - if (validator.description != null) - result += '- ${validator.description} '; + FlutterVersion version = FlutterVersion.getVersion(); - if (type == ValidationType.missing) - result += '(missing)'; - else if (type == ValidationType.installed) - result += '(installed)'; + messages.add(new ValidationMessage('Flutter root at ${version.flutterRoot}')); + messages.add(new ValidationMessage('Framework revision ${version.frameworkRevisionShort} ' + '(${version.frameworkAge})')); + messages.add(new ValidationMessage('Engine revision ${version.engineRevisionShort}')); - printStatus(result); - - if (type == ValidationType.missing && validator.resolution != null) - printStatus('$indent ${validator.resolution}'); - } - - List<ValidationResult> get _allResults { - List<ValidationResult> results = <ValidationResult>[this]; - results.addAll(childResults); - return results; + return new ValidationResult(ValidationType.installed, messages, + statusInfo: '${version.frameworkRevisionShort} - channel ${version.channel}'); } } 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; if (env['ATOM_HOME'] != null) return env['ATOM_HOME']; @@ -229,42 +200,42 @@ } @override - String get label => 'Atom development environment'; - - @override ValidationResult validate() { - Validator atomValidator = new Validator( - label, - description: 'a lightweight development environment for Flutter' + List<ValidationMessage> messages = <ValidationMessage>[]; + + int installCount = 0; + + bool atomDirExists = FileSystemEntity.isDirectorySync(_getAtomHomePath()); + if (!atomDirExists) { + messages.add(new ValidationMessage.error( + 'Atom not installed; download at https://atom.io.' + )); + } else { + installCount++; + } + + String flutterPluginPath = path.join(_getAtomHomePath(), 'packages', 'flutter'); + if (!FileSystemEntity.isDirectorySync(flutterPluginPath)) { + messages.add(new ValidationMessage.error( + 'Flutter plugin not installed; this adds Flutter specific functionality to Atom.\n' + 'Install the \'flutter\' plugin in Atom or run \'apm install flutter\'.' + )); + } else { + installCount++; + + 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'); + } + } + + return new ValidationResult( + installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing, + messages ); - - ValidationType atomExists() { - bool atomDirExists = FileSystemEntity.isDirectorySync(getAtomHomePath()); - return atomDirExists ? ValidationType.installed : ValidationType.missing; - }; - - ValidationType flutterPluginExists() { - 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 - )); - - atomValidator.addValidator(new Validator( - 'Flutter plugin', - description: 'adds Flutter specific functionality to Atom', - resolution: "Install the 'flutter' plugin in Atom or run 'apm install flutter'", - validatorFunction: flutterPluginExists - )); - - return atomValidator.validate(); } - - @override - void diagnose() => validate().print(); }
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 33af902..d005f32 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -79,7 +79,7 @@ bool get supportsStartPaused => false; static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) { - if (!doctor.iosWorkflow.hasIdeviceId) + if (!doctor.iosWorkflow.hasIDeviceId) return <IOSDevice>[]; List<IOSDevice> devices = [];
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart index 10b1447..8df90c7 100644 --- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart +++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -8,112 +8,96 @@ 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; + + if (xcode.isInstalled) { + installCount++; + + xcodeVersionInfo = xcode.xcodeVersionText; + if (xcodeVersionInfo.contains(',')) + xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(',')); + + 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.' + )); + } + + 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/.' + )); + } + + // brew installed + if (exitsHappy(<String>['brew', '-v'])) { + installCount++; + + List<String> installed = <String>[]; + + 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'); + } + + 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\'.' + )); + } else { + installed.add('ios-deploy'); + } + + 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 new ValidationResult( + installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing, + messages, + statusInfo: 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 - ); - - iosValidator.addValidator(xcodeValidator); - - 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 - )); - - xcodeValidator.addValidator(new Validator( - 'EULA', - description: 'XCode end user license agreement', - resolution: "Open XCode or run the command 'sudo xcodebuild -license'", - validatorFunction: xcodeEulaSigned - )); - - Validator brewValidator = new Validator( - 'brew', - description: 'install additional development packages', - resolution: 'Download at http://brew.sh/', - validatorFunction: brewExists - ); - - iosValidator.addValidator(brewValidator); - - brewValidator.addValidator(new Validator( - 'ideviceinstaller', - description: 'discover connected iOS devices', - resolution: "Install via 'brew install ideviceinstaller'", - validatorFunction: ideviceinstallerExists - )); - - brewValidator.addValidator(new Validator( - 'ios-deploy', - description: 'deploy to connected iOS devices', - resolution: "Install via 'brew install ios-deploy'", - validatorFunction: iosdeployExists - )); - - return iosValidator.validate(); - } - - @override - void diagnose() => validate().print(); - - bool get hasIdeviceId => exitsHappy(<String>['idevice_id', '-h']); - - /// 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; } }
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index cd72155..388cc12 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -54,27 +54,24 @@ } } - bool _xcodeVersionSatisfactory; + String _xcodeVersionText; + + String get xcodeVersionText { + if (_xcodeVersionText == null) + _xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', '); + return _xcodeVersionText; + } + bool get xcodeVersionSatisfactory { - if (_xcodeVersionSatisfactory != null) - return _xcodeVersionSatisfactory; + RegExp regex = new RegExp(r'Xcode ([0-9.]+)'); - try { - String output = runSync(<String>['xcodebuild', '-version']); - RegExp regex = new RegExp(r'Xcode ([0-9.]+)'); + String version = regex.firstMatch(xcodeVersionText).group(1); + List<String> components = version.split('.'); - String version = regex.firstMatch(output).group(1); - List<String> components = version.split('.'); + int major = int.parse(components[0]); + int minor = components.length == 1 ? 0 : int.parse(components[1]); - 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); } }
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index 4f6d55f..6891e57 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -200,16 +200,8 @@ } } - 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); }
diff --git a/packages/flutter_tools/lib/src/runner/version.dart b/packages/flutter_tools/lib/src/runner/version.dart index f3cc691..e5dd189 100644 --- a/packages/flutter_tools/lib/src/runner/version.dart +++ b/packages/flutter_tools/lib/src/runner/version.dart
@@ -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('/'); - 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); +class FlutterVersion { + FlutterVersion(this.flutterRoot) { + _channel = _runGit('git rev-parse --abbrev-ref --symbolic @{u}'); + + int slash = _channel.indexOf('/'); + if (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; - return '$from\n$flutterVersion\n$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; + + 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); + } }