doctor.dart 7.7 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

Devon Carew's avatar
Devon Carew committed
5 6
import 'dart:io';

7 8
import 'package:path/path.dart' as path;

9 10
import 'android/android_workflow.dart';
import 'base/context.dart';
11
import 'base/os.dart';
12
import 'globals.dart';
13 14
import 'ios/ios_workflow.dart';

Devon Carew's avatar
Devon Carew committed
15 16
// TODO(devoncarew): Make it easy to add version information to the `doctor` printout.

17 18 19 20
class Doctor {
  Doctor() {
    _iosWorkflow = new IOSWorkflow();
    if (_iosWorkflow.appliesToHostPlatform)
Devon Carew's avatar
Devon Carew committed
21
      _validators.add(_iosWorkflow);
22 23 24

    _androidWorkflow = new AndroidWorkflow();
    if (_androidWorkflow.appliesToHostPlatform)
Devon Carew's avatar
Devon Carew committed
25 26 27
      _validators.add(_androidWorkflow);

    _validators.add(new _AtomValidator());
28 29 30 31 32 33 34 35 36 37 38 39 40 41
  }

  static void initGlobal() {
    context[Doctor] = new Doctor();
  }

  IOSWorkflow _iosWorkflow;
  AndroidWorkflow _androidWorkflow;

  /// This can return null for platforms that don't support developing for iOS.
  IOSWorkflow get iosWorkflow => _iosWorkflow;

  AndroidWorkflow get androidWorkflow => _androidWorkflow;

Devon Carew's avatar
Devon Carew committed
42
  List<DoctorValidator> _validators = <DoctorValidator>[];
43

44 45 46
  List<Workflow> get workflows {
    return new List<Workflow>.from(_validators.where((DoctorValidator validator) => validator is Workflow));
  }
47

48 49 50 51 52 53 54 55
  /// Print a summary of the state of the tooling, as well as how to get more info.
  void summary() => printStatus(summaryText);

  String get summaryText {
    StringBuffer buffer = new StringBuffer();

    bool allGood = true;

Devon Carew's avatar
Devon Carew committed
56 57 58
    for (DoctorValidator validator in _validators) {
      ValidationResult result = validator.validate();
      buffer.write('${result.leadingBox} The ${validator.label} is ');
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
      if (result.type == ValidationType.missing)
        buffer.writeln('not installed.');
      else if (result.type == ValidationType.partial)
        buffer.writeln('partially installed; more components are available.');
      else
        buffer.writeln('fully installed.');
      if (result.type != ValidationType.installed)
        allGood = false;
    }

    if (!allGood) {
      buffer.writeln();
      buffer.write('Run "flutter doctor" for information about installing additional components.');
    }

    return buffer.toString();
  }

77 78
  /// Print verbose information about the state of installed tooling.
  void diagnose() {
Devon Carew's avatar
Devon Carew committed
79 80 81
    bool firstLine = true;
    for (DoctorValidator validator in _validators) {
      if (!firstLine)
82
        printStatus('');
Devon Carew's avatar
Devon Carew committed
83 84
      firstLine = false;
      validator.diagnose();
85 86 87 88 89 90 91 92
    }
  }

  bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices);

  bool get canLaunchAnything => workflows.any((Workflow workflow) => workflow.canLaunchDevices);
}

Devon Carew's avatar
Devon Carew committed
93 94
abstract class DoctorValidator {
  String get label;
95

Devon Carew's avatar
Devon Carew committed
96
  ValidationResult validate();
97

Devon Carew's avatar
Devon Carew committed
98 99 100 101 102 103
  /// 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 {
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  /// Whether the workflow applies to this platform (as in, should we ever try and use it).
  bool get appliesToHostPlatform;

  /// Are we functional enough to list devices?
  bool get canListDevices;

  /// Could this thing launch *something*? It may still have minor issues.
  bool get canLaunchDevices;
}

enum ValidationType {
  missing,
  partial,
  installed
}

typedef ValidationType ValidationFunction();

class Validator {
  Validator(this.name, { this.description, this.resolution, this.validatorFunction });

  final String name;
  final String description;
  final String resolution;
  final ValidationFunction validatorFunction;

130
  List<Validator> _children = <Validator>[];
131 132

  ValidationResult validate() {
133 134
    List<ValidationResult> childResults;
    ValidationType type;
135

136 137 138 139 140 141 142 143 144 145 146 147 148 149
    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)
      );
    }
150

151
    return new ValidationResult(type, this, childResults);
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  }

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

class ValidationResult {
  ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]);

  final ValidationType type;
  final Validator validator;
  final List<ValidationResult> childResults;

172 173 174 175 176 177 178 179 180
  String get leadingBox {
    if (type == ValidationType.missing)
      return '[ ]';
    else if (type == ValidationType.installed)
      return '[✓]';
    else
      return '[-]';
  }

181 182 183 184 185 186 187
  void print([String indent = '']) {
    printSelf(indent);

    for (ValidationResult child in childResults)
      child.print(indent + '  ');
  }

188
  void printSelf([String indent = '']) {
189 190 191
    String result = indent;

    if (type == ValidationType.missing)
192
      result += '$leadingBox ';
193
    else if (type == ValidationType.installed)
194
      result += '$leadingBox ';
195
    else
196
      result += '$leadingBox ';
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212

    result += '${validator.name} ';

    if (validator.description != null)
      result += '- ${validator.description} ';

    if (type == ValidationType.missing)
      result += '(missing)';
    else if (type == ValidationType.installed)
      result += '(installed)';

    printStatus(result);

    if (type == ValidationType.missing && validator.resolution != null)
      printStatus('$indent    ${validator.resolution}');
  }
213 214 215 216 217 218

  List<ValidationResult> get _allResults {
    List<ValidationResult> results = <ValidationResult>[this];
    results.addAll(childResults);
    return results;
  }
219
}
Devon Carew's avatar
Devon Carew committed
220 221

class _AtomValidator extends DoctorValidator {
222 223 224 225 226 227 228 229 230
  static String getAtomHomePath() {
    final Map<String, String> env = Platform.environment;
    if (env['ATOM_HOME'] != null)
      return env['ATOM_HOME'];
    return os.isWindows
      ? path.join(env['USERPROFILE'], '.atom')
      : path.join(env['HOME'], '.atom');
  }

231
  @override
Devon Carew's avatar
Devon Carew committed
232 233
  String get label => 'Atom development environment';

234
  @override
Devon Carew's avatar
Devon Carew committed
235 236 237 238 239 240 241
  ValidationResult validate() {
    Validator atomValidator = new Validator(
      label,
      description: 'a lightweight development environment for Flutter'
    );

    ValidationType atomExists() {
242 243
      bool atomDirExists = FileSystemEntity.isDirectorySync(getAtomHomePath());
      return atomDirExists ? ValidationType.installed : ValidationType.missing;
Devon Carew's avatar
Devon Carew committed
244 245 246
    };

    ValidationType flutterPluginExists() {
247 248 249
      String flutterPluginPath = path.join(getAtomHomePath(), 'packages', 'flutter');
      bool flutterPluginExists = FileSystemEntity.isDirectorySync(flutterPluginPath);
      return flutterPluginExists ? ValidationType.installed : ValidationType.missing;
Devon Carew's avatar
Devon Carew committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
    };

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

268
  @override
Devon Carew's avatar
Devon Carew committed
269 270
  void diagnose() => validate().print();
}