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);
+  }
 }