doctor.dart 18.6 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 'doctor_validator.dart';
25
import 'features.dart';
26
import 'fuchsia/fuchsia_workflow.dart';
27
import 'globals.dart' as globals;
28
import 'intellij/intellij_validator.dart';
29
import 'linux/linux_doctor.dart';
30 31
import 'linux/linux_workflow.dart';
import 'macos/macos_workflow.dart';
32
import 'macos/xcode_validator.dart';
33
import 'proxy_validator.dart';
34
import 'reporting/reporting.dart';
35
import 'tester/flutter_tester.dart';
36
import 'version.dart';
37
import 'vscode/vscode_validator.dart';
38
import 'web/chrome.dart';
39 40
import 'web/web_validator.dart';
import 'web/workflow.dart';
41
import 'windows/visual_studio_validator.dart';
42
import 'windows/windows_workflow.dart';
Devon Carew's avatar
Devon Carew committed
43

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

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

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

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

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

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

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

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

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

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

146 147
      if (globals.iosWorkflow.appliesToHostPlatform) {
        _workflows.add(globals.iosWorkflow);
148
      }
149

150
      if (androidWorkflow.appliesToHostPlatform) {
151
        _workflows.add(androidWorkflow);
152
      }
153

154
      if (fuchsiaWorkflow.appliesToHostPlatform) {
155
        _workflows.add(fuchsiaWorkflow);
156
      }
157

158
      if (linuxWorkflow.appliesToHostPlatform) {
159
        _workflows.add(linuxWorkflow);
160
      }
161

162
      if (macOSWorkflow.appliesToHostPlatform) {
163
        _workflows.add(macOSWorkflow);
164
      }
165

166
      if (windowsWorkflow.appliesToHostPlatform) {
167
        _workflows.add(windowsWorkflow);
168
      }
169

170
      if (webWorkflow.appliesToHostPlatform) {
171
        _workflows.add(webWorkflow);
172
      }
173

174
    }
175 176
    return _workflows;
  }
177 178 179
}

class Doctor {
180 181 182 183 184
  Doctor({
    @required Logger logger,
  }) : _logger = logger;

  final Logger _logger;
185 186 187 188

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

190 191
  /// Return a list of [ValidatorTask] objects and starts validation on all
  /// objects in [validators].
192
  List<ValidatorTask> startValidatorTasks() => <ValidatorTask>[
193
    for (final DoctorValidator validator in validators)
194 195 196 197 198 199 200 201 202 203 204 205 206 207
      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);
          },
        ),
      ),
  ];
208

209
  List<Workflow> get workflows {
210
    return DoctorValidatorsProvider.instance.workflows;
211
  }
212

213
  /// Print a summary of the state of the tooling, as well as how to get more info.
214
  Future<void> summary() async {
215
    _logger.printStatus(await _summaryText());
216
  }
217

218
  Future<String> _summaryText() async {
219
    final StringBuffer buffer = StringBuffer();
220

221 222
    bool missingComponent = false;
    bool sawACrash = false;
223

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

253
      if (result.statusInfo != null) {
254
        lineBuffer.write(' (${result.statusInfo})');
255
      }
256

257 258 259
      buffer.write(wrapText(
        lineBuffer.toString(),
        hangingIndent: result.leadingBox.length + 1,
260 261
        columnWidth: globals.outputPreferences.wrapColumn,
        shouldWrap: globals.outputPreferences.wrapText,
262
      ));
263 264
      buffer.writeln();

265 266 267 268 269 270 271 272
      if (result.type != ValidationType.installed) {
        missingComponent = true;
      }
    }

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

275
    if (missingComponent) {
276
      buffer.writeln();
Devon Carew's avatar
Devon Carew committed
277
      buffer.writeln('Run "flutter doctor" for information about installing additional components.');
278 279 280 281 282
    }

    return buffer.toString();
  }

283
  Future<bool> checkRemoteArtifacts(String engineRevision) async {
284
    return globals.cache.areRemoteArtifactsAvailable(engineVersion: engineRevision);
285 286
  }

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

298
    if (!verbose) {
299
      _logger.printStatus('Doctor summary (to see all details, run flutter doctor -v):');
300
    }
301
    bool doctorResult = true;
302
    int issues = 0;
303

304
    for (final ValidatorTask validatorTask in startValidatorTasks()) {
305
      final DoctorValidator validator = validatorTask.validator;
306
      final Status status = _logger.startSpinner();
307
      ValidationResult result;
308 309
      try {
        result = await validatorTask.result;
310
        status.stop();
311 312 313
      } on Exception catch (exception, stackTrace) {
        result = ValidationResult.crash(exception, stackTrace);
        status.cancel();
314
      }
315

316
      switch (result.type) {
317 318 319 320
        case ValidationType.crash:
          doctorResult = false;
          issues += 1;
          break;
321 322 323 324 325 326 327 328 329 330
        case ValidationType.missing:
          doctorResult = false;
          issues += 1;
          break;
        case ValidationType.partial:
        case ValidationType.notAvailable:
          issues += 1;
          break;
        case ValidationType.installed:
          break;
331
      }
332

333
      DoctorResultEvent(validator: validator, result: result).send();
334

335
      final String leadingBox = showColor ? result.coloredLeadingBox : result.leadingBox;
336
      if (result.statusInfo != null) {
337
        _logger.printStatus('$leadingBox ${validator.title} (${result.statusInfo})',
338 339
            hangingIndent: result.leadingBox.length + 1);
      } else {
340
        _logger.printStatus('$leadingBox ${validator.title}',
341 342
            hangingIndent: result.leadingBox.length + 1);
      }
343

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

365
    // Make sure there's always one line before the summary even when not verbose.
366
    if (!verbose) {
367
      _logger.printStatus('');
368
    }
369

370
    if (issues > 0) {
371
      _logger.printStatus('${showColor ? globals.terminal.color('!', TerminalColor.yellow) : '!'}'
372
        ' Doctor found issues in $issues categor${issues > 1 ? "ies" : "y"}.', hangingIndent: 2);
373
    } else {
374
      _logger.printStatus('${showColor ? globals.terminal.color('•', TerminalColor.green) : '•'}'
375
        ' No issues found!', hangingIndent: 2);
376
    }
377 378

    return doctorResult;
379 380 381 382
  }

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

383
  bool get canLaunchAnything {
384
    if (FlutterTesterDevices.showFlutterTesterDevice) {
385
      return true;
386
    }
387 388
    return workflows.any((Workflow workflow) => workflow.canLaunchDevices);
  }
389 390
}

391
/// A validator that checks the version of Flutter, as well as some auxiliary information
392 393 394 395
/// such as the pub or Flutter cache overrides.
///
/// This is primarily useful for diagnosing issues on Github bug reports by displaying
/// specific commit information.
396
class FlutterValidator extends DoctorValidator {
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
  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;
424

425
  @override
426
  Future<ValidationResult> validate() async {
427
    final List<ValidationMessage> messages = <ValidationMessage>[];
428
    ValidationType valid = ValidationType.installed;
429 430 431 432
    String versionChannel;
    String frameworkVersion;

    try {
433
      final FlutterVersion version = _flutterVersion();
434 435
      versionChannel = version.channel;
      frameworkVersion = version.frameworkVersion;
436
      messages.add(ValidationMessage(_userMessages.flutterVersion(
437
        frameworkVersion,
438
        _flutterRoot(),
439
      )));
440 441 442 443
      messages.add(ValidationMessage(_userMessages.flutterUpstreamRepositoryUrl(version.repositoryUrl ?? 'unknown')));
      if (_platform.environment.containsKey('FLUTTER_GIT_URL')) {
        messages.add(ValidationMessage(_userMessages.flutterGitUrl(_platform.environment['FLUTTER_GIT_URL'])));
      }
444
      messages.add(ValidationMessage(_userMessages.flutterRevision(
445 446
        version.frameworkRevisionShort,
        version.frameworkAge,
447
        version.frameworkCommitDate,
448
      )));
449 450 451 452
      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'])));
453
      }
454 455
      if (_platform.environment.containsKey('FLUTTER_STORAGE_BASE_URL')) {
        messages.add(ValidationMessage(_userMessages.flutterMirrorURL(_platform.environment['FLUTTER_STORAGE_BASE_URL'])));
456
      }
457 458 459 460
    } on VersionCheckError catch (e) {
      messages.add(ValidationMessage.error(e.message));
      valid = ValidationType.partial;
    }
461

462
    // Check that the binaries we downloaded for this platform actually run on it.
463 464 465 466 467 468 469 470
    // 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);
471
      }
472
      messages.add(ValidationMessage.error(buffer.toString()));
473
      valid = ValidationType.partial;
474
    }
475

476 477 478
    return ValidationResult(
      valid,
      messages,
479
      statusInfo: _userMessages.flutterStatusInfo(
480 481
        versionChannel,
        frameworkVersion,
482 483
        _operatingSystemUtils.name,
        _platform.localeName,
484
      ),
485
    );
486
  }
Devon Carew's avatar
Devon Carew committed
487

488 489 490 491 492 493 494
  bool _genSnapshotRuns(String genSnapshotPath) {
    const int kExpectedExitCode = 255;
    try {
      return _processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode;
    } on Exception {
      return false;
    }
495 496 497
  }
}

498
class DeviceValidator extends DoctorValidator {
499 500 501 502 503 504 505 506 507 508
  // 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;
509

510 511 512
  @override
  String get slowWarning => 'Scanning for devices is taking a long time...';

513 514
  @override
  Future<ValidationResult> validate() async {
515 516 517 518
    final List<Device> devices = await _deviceManager.getAllConnectedDevices();
    List<ValidationMessage> installedMessages = <ValidationMessage>[];
    if (devices.isNotEmpty) {
      installedMessages = await Device.descriptions(devices)
519
          .map<ValidationMessage>((String msg) => ValidationMessage(msg)).toList();
520
    }
521

522 523 524 525 526 527 528 529
    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)];
    }

530
    if (devices.isEmpty) {
531 532 533 534 535 536 537 538
      return ValidationResult(ValidationType.notAvailable, diagnosticMessages);
    } else if (diagnostics.isNotEmpty) {
      installedMessages.addAll(diagnosticMessages);
      return ValidationResult(
        ValidationType.installed,
        installedMessages,
        statusInfo: _userMessages.devicesAvailable(devices.length)
      );
539
    } else {
540 541 542 543 544
      return ValidationResult(
        ValidationType.installed,
        installedMessages,
        statusInfo: _userMessages.devicesAvailable(devices.length)
      );
545
    }
546 547
  }
}