doctor.dart 27.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7
import 'package:meta/meta.dart';
8
import 'package:process/process.dart';
9

10
import 'android/android_studio_validator.dart';
11
import 'android/android_workflow.dart';
12
import 'artifacts.dart';
13
import 'base/async_guard.dart';
14
import 'base/context.dart';
15
import 'base/file_system.dart';
16
import 'base/logger.dart';
17
import 'base/os.dart';
18
import 'base/platform.dart';
19
import 'base/terminal.dart';
20
import 'base/user_messages.dart';
21
import 'base/utils.dart';
22
import 'cache.dart';
23
import 'device.dart';
24
import 'features.dart';
25
import 'fuchsia/fuchsia_workflow.dart';
26
import 'globals.dart' as globals;
27
import 'intellij/intellij_validator.dart';
28
import 'linux/linux_doctor.dart';
29 30
import 'linux/linux_workflow.dart';
import 'macos/macos_workflow.dart';
31
import 'macos/xcode_validator.dart';
32
import 'proxy_validator.dart';
33
import 'reporting/reporting.dart';
34
import 'tester/flutter_tester.dart';
35
import 'version.dart';
36
import 'vscode/vscode_validator.dart';
37
import 'web/chrome.dart';
38 39
import 'web/web_validator.dart';
import 'web/workflow.dart';
40
import 'windows/visual_studio_validator.dart';
41
import 'windows/windows_workflow.dart';
Devon Carew's avatar
Devon Carew committed
42

43 44
abstract class DoctorValidatorsProvider {
  /// The singleton instance, pulled from the [AppContext].
45
  static DoctorValidatorsProvider get instance => context.get<DoctorValidatorsProvider>();
46

47
  static final DoctorValidatorsProvider defaultInstance = _DefaultDoctorValidatorsProvider();
48 49

  List<DoctorValidator> get validators;
50
  List<Workflow> get workflows;
51 52
}

53
class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
54
  List<DoctorValidator> _validators;
55
  List<Workflow> _workflows;
56

57 58 59 60 61 62 63 64 65 66
  final LinuxWorkflow linuxWorkflow = LinuxWorkflow(
    platform: globals.platform,
    featureFlags: featureFlags,
  );

  final WebWorkflow webWorkflow = WebWorkflow(
    platform: globals.platform,
    featureFlags: featureFlags,
  );

67 68 69 70 71
  final MacOSWorkflow macOSWorkflow = MacOSWorkflow(
    platform: globals.platform,
    featureFlags: featureFlags,
  );

72
  @override
73
  List<DoctorValidator> get validators {
74 75
    if (_validators != null) {
      return _validators;
76
    }
77 78

    final List<DoctorValidator> ideValidators = <DoctorValidator>[
79 80
      if (androidWorkflow.appliesToHostPlatform)
        ...AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages),
81 82 83 84 85 86
      ...IntelliJValidator.installedValidators(
        fileSystem: globals.fs,
        platform: globals.platform,
        userMessages: userMessages,
        plistParser: globals.plistParser,
      ),
87
      ...VsCodeValidator.installedValidators(globals.fs, globals.platform),
88
    ];
89
    final ProxyValidator proxyValidator = ProxyValidator(platform: globals.platform);
90
    _validators = <DoctorValidator>[
91 92 93 94 95 96 97 98 99 100
      FlutterValidator(
        fileSystem: globals.fs,
        platform: globals.platform,
        flutterVersion: () => globals.flutterVersion,
        processManager: globals.processManager,
        userMessages: userMessages,
        artifacts: globals.artifacts,
        flutterRoot: () => Cache.flutterRoot,
        operatingSystemUtils: globals.os,
      ),
101
      if (androidWorkflow.appliesToHostPlatform)
102
        GroupedValidator(<DoctorValidator>[androidValidator, androidLicenseValidator]),
103
      if (globals.iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform)
104
        GroupedValidator(<DoctorValidator>[XcodeValidator(xcode: globals.xcode, userMessages: userMessages), globals.cocoapodsValidator]),
105
      if (webWorkflow.appliesToHostPlatform)
106 107 108 109 110 111 112
        ChromeValidator(
          chromiumLauncher: ChromiumLauncher(
            browserFinder: findChromeExecutable,
            fileSystem: globals.fs,
            operatingSystemUtils: globals.os,
            platform:  globals.platform,
            processManager: globals.processManager,
113
            logger: globals.logger,
114
          ),
115 116
          platform: globals.platform,
        ),
117
      if (linuxWorkflow.appliesToHostPlatform)
118 119
        LinuxDoctorValidator(
          processManager: globals.processManager,
120
          userMessages: userMessages,
121
        ),
122 123 124 125 126 127
      if (windowsWorkflow.appliesToHostPlatform)
        visualStudioValidator,
      if (ideValidators.isNotEmpty)
        ...ideValidators
      else
        NoIdeValidator(),
128 129
      if (proxyValidator.shouldShow)
        proxyValidator,
130 131 132 133 134
      if (globals.deviceManager.canListAnything)
        DeviceValidator(
          deviceManager: globals.deviceManager,
          userMessages: globals.userMessages,
        ),
135
    ];
136 137
    return _validators;
  }
138 139 140

  @override
  List<Workflow> get workflows {
141 142 143
    if (_workflows == null) {
      _workflows = <Workflow>[];

144 145
      if (globals.iosWorkflow.appliesToHostPlatform) {
        _workflows.add(globals.iosWorkflow);
146
      }
147

148
      if (androidWorkflow.appliesToHostPlatform) {
149
        _workflows.add(androidWorkflow);
150
      }
151

152
      if (fuchsiaWorkflow.appliesToHostPlatform) {
153
        _workflows.add(fuchsiaWorkflow);
154
      }
155

156
      if (linuxWorkflow.appliesToHostPlatform) {
157
        _workflows.add(linuxWorkflow);
158
      }
159

160
      if (macOSWorkflow.appliesToHostPlatform) {
161
        _workflows.add(macOSWorkflow);
162
      }
163

164
      if (windowsWorkflow.appliesToHostPlatform) {
165
        _workflows.add(windowsWorkflow);
166
      }
167

168
      if (webWorkflow.appliesToHostPlatform) {
169
        _workflows.add(webWorkflow);
170
      }
171

172
    }
173 174 175
    return _workflows;
  }

176 177 178 179 180 181 182 183 184
}

class ValidatorTask {
  ValidatorTask(this.validator, this.result);
  final DoctorValidator validator;
  final Future<ValidationResult> result;
}

class Doctor {
185 186 187 188 189
  Doctor({
    @required Logger logger,
  }) : _logger = logger;

  final Logger _logger;
190 191 192 193

  List<DoctorValidator> get validators {
    return DoctorValidatorsProvider.instance.validators;
  }
194

195 196
  /// Return a list of [ValidatorTask] objects and starts validation on all
  /// objects in [validators].
197
  List<ValidatorTask> startValidatorTasks() => <ValidatorTask>[
198
    for (final DoctorValidator validator in validators)
199 200 201 202 203 204 205 206 207 208 209 210 211 212
      ValidatorTask(
        validator,
        // We use an asyncGuard() here to be absolutely certain that
        // DoctorValidators do not result in an uncaught exception. Since the
        // Future returned by the asyncGuard() is not awaited, we pass an
        // onError callback to it and translate errors into ValidationResults.
        asyncGuard<ValidationResult>(
          validator.validate,
          onError: (Object exception, StackTrace stackTrace) {
            return ValidationResult.crash(exception, stackTrace);
          },
        ),
      ),
  ];
213

214
  List<Workflow> get workflows {
215
    return DoctorValidatorsProvider.instance.workflows;
216
  }
217

218
  /// Print a summary of the state of the tooling, as well as how to get more info.
219
  Future<void> summary() async {
220
    _logger.printStatus(await _summaryText());
221
  }
222

223
  Future<String> _summaryText() async {
224
    final StringBuffer buffer = StringBuffer();
225

226 227
    bool missingComponent = false;
    bool sawACrash = false;
228

229
    for (final DoctorValidator validator in validators) {
230
      final StringBuffer lineBuffer = StringBuffer();
231 232 233
      ValidationResult result;
      try {
        result = await asyncGuard<ValidationResult>(() => validator.validate());
234
      } on Exception catch (exception) {
235 236 237 238
        // We're generating a summary, so drop the stack trace.
        result = ValidationResult.crash(exception);
      }
      lineBuffer.write('${result.coloredLeadingBox} ${validator.title}: ');
239
      switch (result.type) {
240 241 242 243
        case ValidationType.crash:
          lineBuffer.write('the doctor check crashed without a result.');
          sawACrash = true;
          break;
244
        case ValidationType.missing:
245
          lineBuffer.write('is not installed.');
246 247
          break;
        case ValidationType.partial:
248
          lineBuffer.write('is partially installed; more components are available.');
249 250
          break;
        case ValidationType.notAvailable:
251
          lineBuffer.write('is not available.');
252 253
          break;
        case ValidationType.installed:
254
          lineBuffer.write('is fully installed.');
255 256
          break;
      }
257

258
      if (result.statusInfo != null) {
259
        lineBuffer.write(' (${result.statusInfo})');
260
      }
261

262 263 264
      buffer.write(wrapText(
        lineBuffer.toString(),
        hangingIndent: result.leadingBox.length + 1,
265 266
        columnWidth: globals.outputPreferences.wrapColumn,
        shouldWrap: globals.outputPreferences.wrapText,
267
      ));
268 269
      buffer.writeln();

270 271 272 273 274 275 276 277
      if (result.type != ValidationType.installed) {
        missingComponent = true;
      }
    }

    if (sawACrash) {
      buffer.writeln();
      buffer.writeln('Run "flutter doctor" for information about why a doctor check crashed.');
278 279
    }

280
    if (missingComponent) {
281
      buffer.writeln();
Devon Carew's avatar
Devon Carew committed
282
      buffer.writeln('Run "flutter doctor" for information about installing additional components.');
283 284 285 286 287
    }

    return buffer.toString();
  }

288
  Future<bool> checkRemoteArtifacts(String engineRevision) async {
289
    return globals.cache.areRemoteArtifactsAvailable(engineVersion: engineRevision);
290 291
  }

292
  /// Print information about the state of installed tooling.
293 294 295 296 297 298 299 300
  Future<bool> diagnose({
    bool androidLicenses = false,
    bool verbose = true,
    bool showColor = true,
    AndroidLicenseValidator androidLicenseValidator,
  }) async {
    if (androidLicenses && androidLicenseValidator != null) {
      return androidLicenseValidator.runLicenseManager();
301
    }
302

303
    if (!verbose) {
304
      _logger.printStatus('Doctor summary (to see all details, run flutter doctor -v):');
305
    }
306
    bool doctorResult = true;
307
    int issues = 0;
308

309
    for (final ValidatorTask validatorTask in startValidatorTasks()) {
310
      final DoctorValidator validator = validatorTask.validator;
311
      final Status status = Status.withSpinner(
312
        stopwatch: Stopwatch(),
313
        terminal: globals.terminal,
314
      );
315
      ValidationResult result;
316 317
      try {
        result = await validatorTask.result;
318
        status.stop();
319 320 321
      } on Exception catch (exception, stackTrace) {
        result = ValidationResult.crash(exception, stackTrace);
        status.cancel();
322
      }
323

324
      switch (result.type) {
325 326 327 328
        case ValidationType.crash:
          doctorResult = false;
          issues += 1;
          break;
329 330 331 332 333 334 335 336 337 338
        case ValidationType.missing:
          doctorResult = false;
          issues += 1;
          break;
        case ValidationType.partial:
        case ValidationType.notAvailable:
          issues += 1;
          break;
        case ValidationType.installed:
          break;
339
      }
340

341
      DoctorResultEvent(validator: validator, result: result).send();
342

343
      final String leadingBox = showColor ? result.coloredLeadingBox : result.leadingBox;
344
      if (result.statusInfo != null) {
345
        _logger.printStatus('$leadingBox ${validator.title} (${result.statusInfo})',
346 347
            hangingIndent: result.leadingBox.length + 1);
      } else {
348
        _logger.printStatus('$leadingBox ${validator.title}',
349 350
            hangingIndent: result.leadingBox.length + 1);
      }
351

352
      for (final ValidationMessage message in result.messages) {
353 354 355
        if (message.type != ValidationMessageType.information || verbose == true) {
          int hangingIndent = 2;
          int indent = 4;
356
          final String indicator = showColor ? message.coloredIndicator : message.indicator;
357
          for (final String line in '$indicator ${message.message}'.split('\n')) {
358
            _logger.printStatus(line, hangingIndent: hangingIndent, indent: indent, emphasis: true);
359 360 361
            // Only do hanging indent for the first line.
            hangingIndent = 0;
            indent = 6;
362
          }
363 364 365
          if (message.contextUrl != null) {
            _logger.printStatus('🔨 ${message.contextUrl}', hangingIndent: hangingIndent, indent: indent, emphasis: true);
          }
366 367
        }
      }
368
      if (verbose) {
369
        _logger.printStatus('');
370
      }
371
    }
372

373
    // Make sure there's always one line before the summary even when not verbose.
374
    if (!verbose) {
375
      _logger.printStatus('');
376
    }
377

378
    if (issues > 0) {
379
      _logger.printStatus('${showColor ? globals.terminal.color('!', TerminalColor.yellow) : '!'}'
380
        ' Doctor found issues in $issues categor${issues > 1 ? "ies" : "y"}.', hangingIndent: 2);
381
    } else {
382
      _logger.printStatus('${showColor ? globals.terminal.color('•', TerminalColor.green) : '•'}'
383
        ' No issues found!', hangingIndent: 2);
384
    }
385 386

    return doctorResult;
387 388 389 390
  }

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

391
  bool get canLaunchAnything {
392
    if (FlutterTesterDevices.showFlutterTesterDevice) {
393
      return true;
394
    }
395 396
    return workflows.any((Workflow workflow) => workflow.canLaunchDevices);
  }
397 398
}

Devon Carew's avatar
Devon Carew committed
399
/// A series of tools and required install steps for a target platform (iOS or Android).
400
abstract class Workflow {
401 402
  const Workflow();

403 404 405 406 407 408 409 410
  /// 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;
411 412 413

  /// Are we functional enough to list emulators?
  bool get canListEmulators;
414 415 416
}

enum ValidationType {
417
  crash,
418 419
  missing,
  partial,
420 421
  notAvailable,
  installed,
422 423
}

424 425 426 427 428 429
enum ValidationMessageType {
  error,
  hint,
  information,
}

430
abstract class DoctorValidator {
431
  const DoctorValidator(this.title);
432

433
  /// This is displayed in the CLI.
434
  final String title;
435

436 437
  String get slowWarning => 'This is taking an unexpectedly long time...';

438
  Future<ValidationResult> validate();
439 440
}

441 442 443 444 445
/// A validator that runs other [DoctorValidator]s and combines their output
/// into a single [ValidationResult]. It uses the title of the first validator
/// passed to the constructor and reports the statusInfo of the first validator
/// that provides one. Other titles and statusInfo strings are discarded.
class GroupedValidator extends DoctorValidator {
446
  GroupedValidator(this.subValidators) : super(subValidators[0].title);
447 448 449

  final List<DoctorValidator> subValidators;

450 451
  List<ValidationResult> _subResults;

452
  /// Sub-validator results.
453
  ///
454
  /// To avoid losing information when results are merged, the sub-results are
455
  /// cached on this field when they are available. The results are in the same
456
  /// order as the sub-validator list.
457 458
  List<ValidationResult> get subResults => _subResults;

459 460 461 462
  @override
  String get slowWarning => _currentSlowWarning;
  String _currentSlowWarning = 'Initializing...';

463
  @override
464
  Future<ValidationResult> validate() async {
465
    final List<ValidatorTask> tasks = <ValidatorTask>[
466
      for (final DoctorValidator validator in subValidators)
467 468 469 470 471
        ValidatorTask(
          validator,
          asyncGuard<ValidationResult>(() => validator.validate()),
        ),
    ];
472 473

    final List<ValidationResult> results = <ValidationResult>[];
474
    for (final ValidatorTask subValidator in tasks) {
475
      _currentSlowWarning = subValidator.validator.slowWarning;
476 477
      try {
        results.add(await subValidator.result);
478
      } on Exception catch (exception, stackTrace) {
479
        results.add(ValidationResult.crash(exception, stackTrace));
480
      }
481
    }
482
    _currentSlowWarning = 'Merging results...';
483 484 485 486 487
    return _mergeValidationResults(results);
  }

  ValidationResult _mergeValidationResults(List<ValidationResult> results) {
    assert(results.isNotEmpty, 'Validation results should not be empty');
488
    _subResults = results;
489 490 491 492
    ValidationType mergedType = results[0].type;
    final List<ValidationMessage> mergedMessages = <ValidationMessage>[];
    String statusInfo;

493
    for (final ValidationResult result in results) {
494 495 496 497 498 499 500
      statusInfo ??= result.statusInfo;
      switch (result.type) {
        case ValidationType.installed:
          if (mergedType == ValidationType.missing) {
            mergedType = ValidationType.partial;
          }
          break;
501
        case ValidationType.notAvailable:
502 503 504
        case ValidationType.partial:
          mergedType = ValidationType.partial;
          break;
505
        case ValidationType.crash:
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
        case ValidationType.missing:
          if (mergedType == ValidationType.installed) {
            mergedType = ValidationType.partial;
          }
          break;
        default:
          throw 'Unrecognized validation type: ' + result.type.toString();
      }
      mergedMessages.addAll(result.messages);
    }

    return ValidationResult(mergedType, mergedMessages,
        statusInfo: statusInfo);
  }
}
521

522
@immutable
523
class ValidationResult {
524 525
  /// [ValidationResult.type] should only equal [ValidationResult.installed]
  /// if no [messages] are hints or errors.
526
  const ValidationResult(this.type, this.messages, { this.statusInfo });
527

528
  factory ValidationResult.crash(Object error, [StackTrace stackTrace]) {
529
    return ValidationResult(ValidationType.crash, <ValidationMessage>[
530
      const ValidationMessage.error(
531 532 533
          'Due to an error, the doctor check did not complete. '
          'If the error message below is not helpful, '
          'please let us know about this issue at https://github.com/flutter/flutter/issues.'),
534
      ValidationMessage.error('$error'),
535
      if (stackTrace != null)
536 537
          // Stacktrace is informational. Printed in verbose mode only.
          ValidationMessage('$stackTrace'),
538 539 540
    ], statusInfo: 'the doctor check crashed');
  }

541
  final ValidationType type;
542 543 544
  // A short message about the status.
  final String statusInfo;
  final List<ValidationMessage> messages;
545

546
  String get leadingBox {
547 548
    assert(type != null);
    switch (type) {
549 550
      case ValidationType.crash:
        return '[☠]';
551 552 553 554
      case ValidationType.missing:
        return '[✗]';
      case ValidationType.installed:
        return '[✓]';
555
      case ValidationType.notAvailable:
556 557 558 559
      case ValidationType.partial:
        return '[!]';
    }
    return null;
560
  }
561 562 563 564

  String get coloredLeadingBox {
    assert(type != null);
    switch (type) {
565
      case ValidationType.crash:
566
        return globals.terminal.color(leadingBox, TerminalColor.red);
567
      case ValidationType.missing:
568
        return globals.terminal.color(leadingBox, TerminalColor.red);
569
      case ValidationType.installed:
570
        return globals.terminal.color(leadingBox, TerminalColor.green);
571 572
      case ValidationType.notAvailable:
      case ValidationType.partial:
573
        return globals.terminal.color(leadingBox, TerminalColor.yellow);
574 575 576
    }
    return null;
  }
577 578 579 580 581

  /// The string representation of the type.
  String get typeStr {
    assert(type != null);
    switch (type) {
582 583
      case ValidationType.crash:
        return 'crash';
584 585 586 587 588 589 590 591 592 593 594
      case ValidationType.missing:
        return 'missing';
      case ValidationType.installed:
        return 'installed';
      case ValidationType.notAvailable:
        return 'notAvailable';
      case ValidationType.partial:
        return 'partial';
    }
    return null;
  }
595
}
596

597 598 599 600 601
/// A status line for the flutter doctor validation to display.
///
/// The [message] is required and represents either an informational statement
/// about the particular doctor validation that passed, or more context
/// on the cause and/or solution to the validation failure.
602
@immutable
603
class ValidationMessage {
604
  /// Create a validation message with information for a passing validator.
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  ///
  /// By default this is not displayed unless the doctor is run in
  /// verbose mode.
  ///
  /// The [contextUrl] may be supplied to link to external resources. This
  /// is displayed after the informative message in verbose modes.
  const ValidationMessage(this.message, {this.contextUrl}) : type = ValidationMessageType.information;

  /// Create a validation message with information for a failing validator.
  const ValidationMessage.error(this.message)
    : type = ValidationMessageType.error,
      contextUrl = null;

  /// Create a validation message with information for a partially failing
  /// validator.
  const ValidationMessage.hint(this.message)
    : type = ValidationMessageType.hint,
      contextUrl = null;
623

624
  final ValidationMessageType type;
625 626 627
  final String contextUrl;
  final String message;

628
  bool get isError => type == ValidationMessageType.error;
629

630
  bool get isHint => type == ValidationMessageType.hint;
631

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
  String get indicator {
    switch (type) {
      case ValidationMessageType.error:
        return '✗';
      case ValidationMessageType.hint:
        return '!';
      case ValidationMessageType.information:
        return '•';
    }
    return null;
  }

  String get coloredIndicator {
    switch (type) {
      case ValidationMessageType.error:
647
        return globals.terminal.color(indicator, TerminalColor.red);
648
      case ValidationMessageType.hint:
649
        return globals.terminal.color(indicator, TerminalColor.yellow);
650
      case ValidationMessageType.information:
651
        return globals.terminal.color(indicator, TerminalColor.green);
652 653 654 655
    }
    return null;
  }

656 657
  @override
  String toString() => message;
658 659 660

  @override
  bool operator ==(Object other) {
661 662
    return other is ValidationMessage
        && other.message == message
663 664
        && other.type == type
        && other.contextUrl == contextUrl;
665 666 667
  }

  @override
668
  int get hashCode => type.hashCode ^ message.hashCode ^ contextUrl.hashCode;
669
}
670

671
/// A validator that checks the version of Flutter, as well as some auxiliary information
672 673 674 675
/// such as the pub or Flutter cache overrides.
///
/// This is primarily useful for diagnosing issues on Github bug reports by displaying
/// specific commit information.
676
class FlutterValidator extends DoctorValidator {
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
  FlutterValidator({
    @required Platform platform,
    @required FlutterVersion Function() flutterVersion,
    @required UserMessages userMessages,
    @required FileSystem fileSystem,
    @required Artifacts artifacts,
    @required ProcessManager processManager,
    @required String Function() flutterRoot,
    @required OperatingSystemUtils operatingSystemUtils,
  }) : _flutterVersion = flutterVersion,
       _platform = platform,
       _userMessages = userMessages,
       _fileSystem = fileSystem,
       _artifacts = artifacts,
       _processManager = processManager,
       _flutterRoot = flutterRoot,
       _operatingSystemUtils = operatingSystemUtils,
       super('Flutter');

  final Platform _platform;
  final FlutterVersion Function() _flutterVersion;
  final String Function() _flutterRoot;
  final UserMessages _userMessages;
  final FileSystem _fileSystem;
  final Artifacts _artifacts;
  final ProcessManager _processManager;
  final OperatingSystemUtils _operatingSystemUtils;
704

705
  @override
706
  Future<ValidationResult> validate() async {
707
    final List<ValidationMessage> messages = <ValidationMessage>[];
708
    ValidationType valid = ValidationType.installed;
709 710 711 712
    String versionChannel;
    String frameworkVersion;

    try {
713
      final FlutterVersion version = _flutterVersion();
714 715
      versionChannel = version.channel;
      frameworkVersion = version.frameworkVersion;
716
      messages.add(ValidationMessage(_userMessages.flutterVersion(
717
        frameworkVersion,
718
        _flutterRoot(),
719
      )));
720
      messages.add(ValidationMessage(_userMessages.flutterRevision(
721 722 723 724
        version.frameworkRevisionShort,
        version.frameworkAge,
        version.frameworkDate,
      )));
725 726 727 728
      messages.add(ValidationMessage(_userMessages.engineRevision(version.engineRevisionShort)));
      messages.add(ValidationMessage(_userMessages.dartRevision(version.dartSdkVersion)));
      if (_platform.environment.containsKey('PUB_HOSTED_URL')) {
        messages.add(ValidationMessage(_userMessages.pubMirrorURL(_platform.environment['PUB_HOSTED_URL'])));
729
      }
730 731
      if (_platform.environment.containsKey('FLUTTER_STORAGE_BASE_URL')) {
        messages.add(ValidationMessage(_userMessages.flutterMirrorURL(_platform.environment['FLUTTER_STORAGE_BASE_URL'])));
732
      }
733 734 735 736
    } on VersionCheckError catch (e) {
      messages.add(ValidationMessage.error(e.message));
      valid = ValidationType.partial;
    }
737

738
    // Check that the binaries we downloaded for this platform actually run on it.
739 740 741 742 743 744 745 746
    // If the binaries are not downloaded (because android is not enabled), then do
    // not run this check.
    final String genSnapshotPath = _artifacts.getArtifactPath(Artifact.genSnapshot);
    if (_fileSystem.file(genSnapshotPath).existsSync() && !_genSnapshotRuns(genSnapshotPath)) {
      final StringBuffer buffer = StringBuffer();
      buffer.writeln(_userMessages.flutterBinariesDoNotRun);
      if (_platform.isLinux) {
        buffer.writeln(_userMessages.flutterBinariesLinuxRepairCommands);
747
      }
748
      messages.add(ValidationMessage.error(buffer.toString()));
749
      valid = ValidationType.partial;
750
    }
751

752 753 754
    return ValidationResult(
      valid,
      messages,
755
      statusInfo: _userMessages.flutterStatusInfo(
756 757
        versionChannel,
        frameworkVersion,
758 759
        _operatingSystemUtils.name,
        _platform.localeName,
760
      ),
761
    );
762
  }
Devon Carew's avatar
Devon Carew committed
763

764 765 766 767 768 769 770
  bool _genSnapshotRuns(String genSnapshotPath) {
    const int kExpectedExitCode = 255;
    try {
      return _processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
    } on Exception {
      return false;
    }
771 772 773
  }
}

774
class NoIdeValidator extends DoctorValidator {
775
  NoIdeValidator() : super('Flutter IDE Support');
776 777 778

  @override
  Future<ValidationResult> validate() async {
779 780 781 782 783
    return ValidationResult(
      ValidationType.missing,
      userMessages.noIdeInstallationInfo.map((String ideInfo) => ValidationMessage(ideInfo)).toList(),
      statusInfo: userMessages.noIdeStatusInfo,
    );
784 785 786
  }
}

787
class DeviceValidator extends DoctorValidator {
788 789 790 791 792 793 794 795 796 797
  // TODO(jmagman): Make required once g3 rolls and is updated.
  DeviceValidator({
    DeviceManager deviceManager,
    UserMessages userMessages,
  }) : _deviceManager = deviceManager ?? globals.deviceManager,
       _userMessages = userMessages ?? globals.userMessages,
       super('Connected device');

  final DeviceManager _deviceManager;
  final UserMessages _userMessages;
798

799 800 801
  @override
  String get slowWarning => 'Scanning for devices is taking a long time...';

802 803
  @override
  Future<ValidationResult> validate() async {
804 805 806 807
    final List<Device> devices = await _deviceManager.getAllConnectedDevices();
    List<ValidationMessage> installedMessages = <ValidationMessage>[];
    if (devices.isNotEmpty) {
      installedMessages = await Device.descriptions(devices)
808
          .map<ValidationMessage>((String msg) => ValidationMessage(msg)).toList();
809
    }
810

811 812 813 814 815 816 817 818
    List<ValidationMessage> diagnosticMessages = <ValidationMessage>[];
    final List<String> diagnostics = await _deviceManager.getDeviceDiagnostics();
    if (diagnostics.isNotEmpty) {
      diagnosticMessages = diagnostics.map<ValidationMessage>((String message) => ValidationMessage.hint(message)).toList();
    } else if (devices.isEmpty) {
      diagnosticMessages = <ValidationMessage>[ValidationMessage.hint(_userMessages.devicesMissing)];
    }

819
    if (devices.isEmpty) {
820 821 822 823 824 825 826 827
      return ValidationResult(ValidationType.notAvailable, diagnosticMessages);
    } else if (diagnostics.isNotEmpty) {
      installedMessages.addAll(diagnosticMessages);
      return ValidationResult(
        ValidationType.installed,
        installedMessages,
        statusInfo: _userMessages.devicesAvailable(devices.length)
      );
828
    } else {
829 830 831 832 833
      return ValidationResult(
        ValidationType.installed,
        installedMessages,
        statusInfo: _userMessages.devicesAvailable(devices.length)
      );
834
    }
835 836
  }
}
837 838 839 840

class ValidatorWithResult extends DoctorValidator {
  ValidatorWithResult(String title, this.result) : super(title);

841 842
  final ValidationResult result;

843 844 845
  @override
  Future<ValidationResult> validate() async => result;
}