1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'android/android_studio_validator.dart'; 8import 'android/android_workflow.dart'; 9import 'artifacts.dart'; 10import 'base/common.dart'; 11import 'base/context.dart'; 12import 'base/file_system.dart'; 13import 'base/logger.dart'; 14import 'base/os.dart'; 15import 'base/platform.dart'; 16import 'base/process_manager.dart'; 17import 'base/terminal.dart'; 18import 'base/user_messages.dart'; 19import 'base/utils.dart'; 20import 'base/version.dart'; 21import 'cache.dart'; 22import 'device.dart'; 23import 'fuchsia/fuchsia_workflow.dart'; 24import 'globals.dart'; 25import 'intellij/intellij.dart'; 26import 'ios/ios_workflow.dart'; 27import 'ios/plist_parser.dart'; 28import 'linux/linux_doctor.dart'; 29import 'linux/linux_workflow.dart'; 30import 'macos/cocoapods_validator.dart'; 31import 'macos/macos_workflow.dart'; 32import 'macos/xcode_validator.dart'; 33import 'proxy_validator.dart'; 34import 'reporting/reporting.dart'; 35import 'tester/flutter_tester.dart'; 36import 'version.dart'; 37import 'vscode/vscode_validator.dart'; 38import 'web/web_validator.dart'; 39import 'web/workflow.dart'; 40import 'windows/visual_studio_validator.dart'; 41import 'windows/windows_workflow.dart'; 42 43Doctor get doctor => context.get<Doctor>(); 44 45abstract class DoctorValidatorsProvider { 46 /// The singleton instance, pulled from the [AppContext]. 47 static DoctorValidatorsProvider get instance => context.get<DoctorValidatorsProvider>(); 48 49 static final DoctorValidatorsProvider defaultInstance = _DefaultDoctorValidatorsProvider(); 50 51 List<DoctorValidator> get validators; 52 List<Workflow> get workflows; 53} 54 55class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { 56 List<DoctorValidator> _validators; 57 List<Workflow> _workflows; 58 59 @override 60 List<DoctorValidator> get validators { 61 if (_validators == null) { 62 final List<DoctorValidator> ideValidators = <DoctorValidator>[ 63 ...AndroidStudioValidator.allValidators, 64 ...IntelliJValidator.installedValidators, 65 ...VsCodeValidator.installedValidators, 66 ]; 67 68 _validators = <DoctorValidator>[ 69 FlutterValidator(), 70 if (androidWorkflow.appliesToHostPlatform) 71 GroupedValidator(<DoctorValidator>[androidValidator, androidLicenseValidator]), 72 if (iosWorkflow.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform) 73 GroupedValidator(<DoctorValidator>[xcodeValidator, cocoapodsValidator]), 74 if (webWorkflow.appliesToHostPlatform) 75 const WebValidator(), 76 if (linuxWorkflow.appliesToHostPlatform) 77 LinuxDoctorValidator(), 78 if (windowsWorkflow.appliesToHostPlatform) 79 visualStudioValidator, 80 if (ideValidators.isNotEmpty) 81 ...ideValidators 82 else 83 NoIdeValidator(), 84 if (ProxyValidator.shouldShow) 85 ProxyValidator(), 86 if (deviceManager.canListAnything) 87 DeviceValidator(), 88 ]; 89 } 90 return _validators; 91 } 92 93 @override 94 List<Workflow> get workflows { 95 if (_workflows == null) { 96 _workflows = <Workflow>[]; 97 98 if (iosWorkflow.appliesToHostPlatform) 99 _workflows.add(iosWorkflow); 100 101 if (androidWorkflow.appliesToHostPlatform) 102 _workflows.add(androidWorkflow); 103 104 if (fuchsiaWorkflow.appliesToHostPlatform) 105 _workflows.add(fuchsiaWorkflow); 106 107 if (linuxWorkflow.appliesToHostPlatform) 108 _workflows.add(linuxWorkflow); 109 110 if (macOSWorkflow.appliesToHostPlatform) 111 _workflows.add(macOSWorkflow); 112 113 if (windowsWorkflow.appliesToHostPlatform) 114 _workflows.add(windowsWorkflow); 115 116 } 117 return _workflows; 118 } 119 120} 121 122class ValidatorTask { 123 ValidatorTask(this.validator, this.result); 124 final DoctorValidator validator; 125 final Future<ValidationResult> result; 126} 127 128class Doctor { 129 const Doctor(); 130 131 List<DoctorValidator> get validators { 132 return DoctorValidatorsProvider.instance.validators; 133 } 134 135 /// Return a list of [ValidatorTask] objects and starts validation on all 136 /// objects in [validators]. 137 List<ValidatorTask> startValidatorTasks() { 138 final List<ValidatorTask> tasks = <ValidatorTask>[]; 139 for (DoctorValidator validator in validators) { 140 tasks.add(ValidatorTask(validator, validator.validate())); 141 } 142 return tasks; 143 } 144 145 List<Workflow> get workflows { 146 return DoctorValidatorsProvider.instance.workflows; 147 } 148 149 /// Print a summary of the state of the tooling, as well as how to get more info. 150 Future<void> summary() async { 151 printStatus(await summaryText); 152 } 153 154 Future<String> get summaryText async { 155 final StringBuffer buffer = StringBuffer(); 156 157 bool allGood = true; 158 159 for (DoctorValidator validator in validators) { 160 final StringBuffer lineBuffer = StringBuffer(); 161 final ValidationResult result = await validator.validate(); 162 lineBuffer.write('${result.coloredLeadingBox} ${validator.title} is '); 163 switch (result.type) { 164 case ValidationType.missing: 165 lineBuffer.write('not installed.'); 166 break; 167 case ValidationType.partial: 168 lineBuffer.write('partially installed; more components are available.'); 169 break; 170 case ValidationType.notAvailable: 171 lineBuffer.write('not available.'); 172 break; 173 case ValidationType.installed: 174 lineBuffer.write('fully installed.'); 175 break; 176 } 177 178 if (result.statusInfo != null) 179 lineBuffer.write(' (${result.statusInfo})'); 180 181 buffer.write(wrapText(lineBuffer.toString(), hangingIndent: result.leadingBox.length + 1)); 182 buffer.writeln(); 183 184 if (result.type != ValidationType.installed) 185 allGood = false; 186 } 187 188 if (!allGood) { 189 buffer.writeln(); 190 buffer.writeln('Run "flutter doctor" for information about installing additional components.'); 191 } 192 193 return buffer.toString(); 194 } 195 196 Future<bool> checkRemoteArtifacts(String engineRevision) async { 197 return Cache.instance.areRemoteArtifactsAvailable(engineVersion: engineRevision); 198 } 199 200 /// Print information about the state of installed tooling. 201 Future<bool> diagnose({ bool androidLicenses = false, bool verbose = true }) async { 202 if (androidLicenses) 203 return AndroidLicenseValidator.runLicenseManager(); 204 205 if (!verbose) { 206 printStatus('Doctor summary (to see all details, run flutter doctor -v):'); 207 } 208 bool doctorResult = true; 209 int issues = 0; 210 211 for (ValidatorTask validatorTask in startValidatorTasks()) { 212 final DoctorValidator validator = validatorTask.validator; 213 final Status status = Status.withSpinner( 214 timeout: timeoutConfiguration.fastOperation, 215 slowWarningCallback: () => validator.slowWarning, 216 ); 217 ValidationResult result; 218 try { 219 result = await validatorTask.result; 220 } catch (exception) { 221 status.cancel(); 222 rethrow; 223 } 224 status.stop(); 225 226 switch (result.type) { 227 case ValidationType.missing: 228 doctorResult = false; 229 issues += 1; 230 break; 231 case ValidationType.partial: 232 case ValidationType.notAvailable: 233 issues += 1; 234 break; 235 case ValidationType.installed: 236 break; 237 } 238 239 DoctorResultEvent(validator: validator, result: result).send(); 240 241 if (result.statusInfo != null) { 242 printStatus('${result.coloredLeadingBox} ${validator.title} (${result.statusInfo})', 243 hangingIndent: result.leadingBox.length + 1); 244 } else { 245 printStatus('${result.coloredLeadingBox} ${validator.title}', 246 hangingIndent: result.leadingBox.length + 1); 247 } 248 249 for (ValidationMessage message in result.messages) { 250 if (message.type != ValidationMessageType.information || verbose == true) { 251 int hangingIndent = 2; 252 int indent = 4; 253 for (String line in '${message.coloredIndicator} ${message.message}'.split('\n')) { 254 printStatus(line, hangingIndent: hangingIndent, indent: indent, emphasis: true); 255 // Only do hanging indent for the first line. 256 hangingIndent = 0; 257 indent = 6; 258 } 259 } 260 } 261 if (verbose) 262 printStatus(''); 263 } 264 265 // Make sure there's always one line before the summary even when not verbose. 266 if (!verbose) 267 printStatus(''); 268 269 if (issues > 0) { 270 printStatus('${terminal.color('!', TerminalColor.yellow)} Doctor found issues in $issues categor${issues > 1 ? "ies" : "y"}.', hangingIndent: 2); 271 } else { 272 printStatus('${terminal.color('•', TerminalColor.green)} No issues found!', hangingIndent: 2); 273 } 274 275 return doctorResult; 276 } 277 278 bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices); 279 280 bool get canLaunchAnything { 281 if (FlutterTesterDevices.showFlutterTesterDevice) 282 return true; 283 return workflows.any((Workflow workflow) => workflow.canLaunchDevices); 284 } 285} 286 287/// A series of tools and required install steps for a target platform (iOS or Android). 288abstract class Workflow { 289 const Workflow(); 290 291 /// Whether the workflow applies to this platform (as in, should we ever try and use it). 292 bool get appliesToHostPlatform; 293 294 /// Are we functional enough to list devices? 295 bool get canListDevices; 296 297 /// Could this thing launch *something*? It may still have minor issues. 298 bool get canLaunchDevices; 299 300 /// Are we functional enough to list emulators? 301 bool get canListEmulators; 302} 303 304enum ValidationType { 305 missing, 306 partial, 307 notAvailable, 308 installed, 309} 310 311enum ValidationMessageType { 312 error, 313 hint, 314 information, 315} 316 317abstract class DoctorValidator { 318 const DoctorValidator(this.title); 319 320 /// This is displayed in the CLI. 321 final String title; 322 323 String get slowWarning => 'This is taking an unexpectedly long time...'; 324 325 Future<ValidationResult> validate(); 326} 327 328/// A validator that runs other [DoctorValidator]s and combines their output 329/// into a single [ValidationResult]. It uses the title of the first validator 330/// passed to the constructor and reports the statusInfo of the first validator 331/// that provides one. Other titles and statusInfo strings are discarded. 332class GroupedValidator extends DoctorValidator { 333 GroupedValidator(this.subValidators) : super(subValidators[0].title); 334 335 final List<DoctorValidator> subValidators; 336 337 List<ValidationResult> _subResults; 338 339 /// Subvalidator results. 340 /// 341 /// To avoid losing information when results are merged, the subresults are 342 /// cached on this field when they are available. The results are in the same 343 /// order as the subvalidator list. 344 List<ValidationResult> get subResults => _subResults; 345 346 @override 347 String get slowWarning => _currentSlowWarning; 348 String _currentSlowWarning = 'Initializing...'; 349 350 @override 351 Future<ValidationResult> validate() async { 352 final List<ValidatorTask> tasks = <ValidatorTask>[]; 353 for (DoctorValidator validator in subValidators) { 354 tasks.add(ValidatorTask(validator, validator.validate())); 355 } 356 357 final List<ValidationResult> results = <ValidationResult>[]; 358 for (ValidatorTask subValidator in tasks) { 359 _currentSlowWarning = subValidator.validator.slowWarning; 360 results.add(await subValidator.result); 361 } 362 _currentSlowWarning = 'Merging results...'; 363 return _mergeValidationResults(results); 364 } 365 366 ValidationResult _mergeValidationResults(List<ValidationResult> results) { 367 assert(results.isNotEmpty, 'Validation results should not be empty'); 368 _subResults = results; 369 ValidationType mergedType = results[0].type; 370 final List<ValidationMessage> mergedMessages = <ValidationMessage>[]; 371 String statusInfo; 372 373 for (ValidationResult result in results) { 374 statusInfo ??= result.statusInfo; 375 switch (result.type) { 376 case ValidationType.installed: 377 if (mergedType == ValidationType.missing) { 378 mergedType = ValidationType.partial; 379 } 380 break; 381 case ValidationType.notAvailable: 382 case ValidationType.partial: 383 mergedType = ValidationType.partial; 384 break; 385 case ValidationType.missing: 386 if (mergedType == ValidationType.installed) { 387 mergedType = ValidationType.partial; 388 } 389 break; 390 default: 391 throw 'Unrecognized validation type: ' + result.type.toString(); 392 } 393 mergedMessages.addAll(result.messages); 394 } 395 396 return ValidationResult(mergedType, mergedMessages, 397 statusInfo: statusInfo); 398 } 399} 400 401class ValidationResult { 402 /// [ValidationResult.type] should only equal [ValidationResult.installed] 403 /// if no [messages] are hints or errors. 404 ValidationResult(this.type, this.messages, { this.statusInfo }); 405 406 final ValidationType type; 407 // A short message about the status. 408 final String statusInfo; 409 final List<ValidationMessage> messages; 410 411 String get leadingBox { 412 assert(type != null); 413 switch (type) { 414 case ValidationType.missing: 415 return '[✗]'; 416 case ValidationType.installed: 417 return '[✓]'; 418 case ValidationType.notAvailable: 419 case ValidationType.partial: 420 return '[!]'; 421 } 422 return null; 423 } 424 425 String get coloredLeadingBox { 426 assert(type != null); 427 switch (type) { 428 case ValidationType.missing: 429 return terminal.color(leadingBox, TerminalColor.red); 430 case ValidationType.installed: 431 return terminal.color(leadingBox, TerminalColor.green); 432 case ValidationType.notAvailable: 433 case ValidationType.partial: 434 return terminal.color(leadingBox, TerminalColor.yellow); 435 } 436 return null; 437 } 438 439 /// The string representation of the type. 440 String get typeStr { 441 assert(type != null); 442 switch (type) { 443 case ValidationType.missing: 444 return 'missing'; 445 case ValidationType.installed: 446 return 'installed'; 447 case ValidationType.notAvailable: 448 return 'notAvailable'; 449 case ValidationType.partial: 450 return 'partial'; 451 } 452 return null; 453 } 454} 455 456class ValidationMessage { 457 ValidationMessage(this.message) : type = ValidationMessageType.information; 458 ValidationMessage.error(this.message) : type = ValidationMessageType.error; 459 ValidationMessage.hint(this.message) : type = ValidationMessageType.hint; 460 461 final ValidationMessageType type; 462 bool get isError => type == ValidationMessageType.error; 463 bool get isHint => type == ValidationMessageType.hint; 464 final String message; 465 466 String get indicator { 467 switch (type) { 468 case ValidationMessageType.error: 469 return '✗'; 470 case ValidationMessageType.hint: 471 return '!'; 472 case ValidationMessageType.information: 473 return '•'; 474 } 475 return null; 476 } 477 478 String get coloredIndicator { 479 switch (type) { 480 case ValidationMessageType.error: 481 return terminal.color(indicator, TerminalColor.red); 482 case ValidationMessageType.hint: 483 return terminal.color(indicator, TerminalColor.yellow); 484 case ValidationMessageType.information: 485 return terminal.color(indicator, TerminalColor.green); 486 } 487 return null; 488 } 489 490 @override 491 String toString() => message; 492 493 @override 494 bool operator ==(Object other) { 495 if (other.runtimeType != runtimeType) { 496 return false; 497 } 498 final ValidationMessage typedOther = other; 499 return typedOther.message == message 500 && typedOther.type == type; 501 } 502 503 @override 504 int get hashCode => type.hashCode ^ message.hashCode; 505} 506 507class FlutterValidator extends DoctorValidator { 508 FlutterValidator() : super('Flutter'); 509 510 @override 511 Future<ValidationResult> validate() async { 512 final List<ValidationMessage> messages = <ValidationMessage>[]; 513 ValidationType valid = ValidationType.installed; 514 515 final FlutterVersion version = FlutterVersion.instance; 516 517 messages.add(ValidationMessage(userMessages.flutterVersion(version.frameworkVersion, Cache.flutterRoot))); 518 messages.add(ValidationMessage(userMessages.flutterRevision(version.frameworkRevisionShort, version.frameworkAge, version.frameworkDate))); 519 messages.add(ValidationMessage(userMessages.engineRevision(version.engineRevisionShort))); 520 messages.add(ValidationMessage(userMessages.dartRevision(version.dartSdkVersion))); 521 final String genSnapshotPath = 522 artifacts.getArtifactPath(Artifact.genSnapshot); 523 524 // Check that the binaries we downloaded for this platform actually run on it. 525 if (!_genSnapshotRuns(genSnapshotPath)) { 526 final StringBuffer buf = StringBuffer(); 527 buf.writeln(userMessages.flutterBinariesDoNotRun); 528 if (platform.isLinux) { 529 buf.writeln(userMessages.flutterBinariesLinuxRepairCommands); 530 } 531 messages.add(ValidationMessage.error(buf.toString())); 532 valid = ValidationType.partial; 533 } 534 535 return ValidationResult(valid, messages, 536 statusInfo: userMessages.flutterStatusInfo(version.channel, version.frameworkVersion, os.name, platform.localeName), 537 ); 538 } 539} 540 541bool _genSnapshotRuns(String genSnapshotPath) { 542 const int kExpectedExitCode = 255; 543 try { 544 return processManager.runSync(<String>[genSnapshotPath]).exitCode == kExpectedExitCode; 545 } catch (error) { 546 return false; 547 } 548} 549 550class NoIdeValidator extends DoctorValidator { 551 NoIdeValidator() : super('Flutter IDE Support'); 552 553 @override 554 Future<ValidationResult> validate() async { 555 return ValidationResult(ValidationType.missing, <ValidationMessage>[ 556 ValidationMessage(userMessages.noIdeInstallationInfo), 557 ], statusInfo: userMessages.noIdeStatusInfo); 558 } 559} 560 561abstract class IntelliJValidator extends DoctorValidator { 562 IntelliJValidator(String title, this.installPath) : super(title); 563 564 final String installPath; 565 566 String get version; 567 String get pluginsPath; 568 569 static final Map<String, String> _idToTitle = <String, String>{ 570 'IntelliJIdea': 'IntelliJ IDEA Ultimate Edition', 571 'IdeaIC': 'IntelliJ IDEA Community Edition', 572 }; 573 574 static final Version kMinIdeaVersion = Version(2017, 1, 0); 575 576 static Iterable<DoctorValidator> get installedValidators { 577 if (platform.isLinux || platform.isWindows) 578 return IntelliJValidatorOnLinuxAndWindows.installed; 579 if (platform.isMacOS) 580 return IntelliJValidatorOnMac.installed; 581 return <DoctorValidator>[]; 582 } 583 584 @override 585 Future<ValidationResult> validate() async { 586 final List<ValidationMessage> messages = <ValidationMessage>[]; 587 588 messages.add(ValidationMessage(userMessages.intellijLocation(installPath))); 589 590 final IntelliJPlugins plugins = IntelliJPlugins(pluginsPath); 591 plugins.validatePackage(messages, <String>['flutter-intellij', 'flutter-intellij.jar'], 592 'Flutter', minVersion: IntelliJPlugins.kMinFlutterPluginVersion); 593 plugins.validatePackage(messages, <String>['Dart'], 'Dart'); 594 595 if (_hasIssues(messages)) { 596 messages.add(ValidationMessage(userMessages.intellijPluginInfo)); 597 } 598 599 _validateIntelliJVersion(messages, kMinIdeaVersion); 600 601 return ValidationResult( 602 _hasIssues(messages) ? ValidationType.partial : ValidationType.installed, 603 messages, 604 statusInfo: userMessages.intellijStatusInfo(version)); 605 } 606 607 bool _hasIssues(List<ValidationMessage> messages) { 608 return messages.any((ValidationMessage message) => message.isError); 609 } 610 611 void _validateIntelliJVersion(List<ValidationMessage> messages, Version minVersion) { 612 // Ignore unknown versions. 613 if (minVersion == Version.unknown) 614 return; 615 616 final Version installedVersion = Version.parse(version); 617 if (installedVersion == null) 618 return; 619 620 if (installedVersion < minVersion) { 621 messages.add(ValidationMessage.error(userMessages.intellijMinimumVersion(minVersion.toString()))); 622 } 623 } 624} 625 626class IntelliJValidatorOnLinuxAndWindows extends IntelliJValidator { 627 IntelliJValidatorOnLinuxAndWindows(String title, this.version, String installPath, this.pluginsPath) : super(title, installPath); 628 629 @override 630 final String version; 631 632 @override 633 final String pluginsPath; 634 635 static Iterable<DoctorValidator> get installed { 636 final List<DoctorValidator> validators = <DoctorValidator>[]; 637 if (homeDirPath == null) 638 return validators; 639 640 void addValidator(String title, String version, String installPath, String pluginsPath) { 641 final IntelliJValidatorOnLinuxAndWindows validator = 642 IntelliJValidatorOnLinuxAndWindows(title, version, installPath, pluginsPath); 643 for (int index = 0; index < validators.length; ++index) { 644 final DoctorValidator other = validators[index]; 645 if (other is IntelliJValidatorOnLinuxAndWindows && validator.installPath == other.installPath) { 646 if (validator.version.compareTo(other.version) > 0) 647 validators[index] = validator; 648 return; 649 } 650 } 651 validators.add(validator); 652 } 653 654 for (FileSystemEntity dir in fs.directory(homeDirPath).listSync()) { 655 if (dir is Directory) { 656 final String name = fs.path.basename(dir.path); 657 IntelliJValidator._idToTitle.forEach((String id, String title) { 658 if (name.startsWith('.$id')) { 659 final String version = name.substring(id.length + 1); 660 String installPath; 661 try { 662 installPath = fs.file(fs.path.join(dir.path, 'system', '.home')).readAsStringSync(); 663 } catch (e) { 664 // ignored 665 } 666 if (installPath != null && fs.isDirectorySync(installPath)) { 667 final String pluginsPath = fs.path.join(dir.path, 'config', 'plugins'); 668 addValidator(title, version, installPath, pluginsPath); 669 } 670 } 671 }); 672 } 673 } 674 return validators; 675 } 676} 677 678class IntelliJValidatorOnMac extends IntelliJValidator { 679 IntelliJValidatorOnMac(String title, this.id, String installPath) : super(title, installPath); 680 681 final String id; 682 683 static final Map<String, String> _dirNameToId = <String, String>{ 684 'IntelliJ IDEA.app': 'IntelliJIdea', 685 'IntelliJ IDEA Ultimate.app': 'IntelliJIdea', 686 'IntelliJ IDEA CE.app': 'IdeaIC', 687 }; 688 689 static Iterable<DoctorValidator> get installed { 690 final List<DoctorValidator> validators = <DoctorValidator>[]; 691 final List<String> installPaths = <String>['/Applications', fs.path.join(homeDirPath, 'Applications')]; 692 693 void checkForIntelliJ(Directory dir) { 694 final String name = fs.path.basename(dir.path); 695 _dirNameToId.forEach((String dirName, String id) { 696 if (name == dirName) { 697 final String title = IntelliJValidator._idToTitle[id]; 698 validators.add(IntelliJValidatorOnMac(title, id, dir.path)); 699 } 700 }); 701 } 702 703 try { 704 final Iterable<Directory> installDirs = installPaths 705 .map<Directory>((String installPath) => fs.directory(installPath)) 706 .map<List<FileSystemEntity>>((Directory dir) => dir.existsSync() ? dir.listSync() : <FileSystemEntity>[]) 707 .expand<FileSystemEntity>((List<FileSystemEntity> mappedDirs) => mappedDirs) 708 .whereType<Directory>(); 709 for (Directory dir in installDirs) { 710 checkForIntelliJ(dir); 711 if (!dir.path.endsWith('.app')) { 712 for (FileSystemEntity subdir in dir.listSync()) { 713 if (subdir is Directory) { 714 checkForIntelliJ(subdir); 715 } 716 } 717 } 718 } 719 } on FileSystemException catch (e) { 720 validators.add(ValidatorWithResult( 721 userMessages.intellijMacUnknownResult, 722 ValidationResult(ValidationType.missing, <ValidationMessage>[ 723 ValidationMessage.error(e.message), 724 ]), 725 )); 726 } 727 return validators; 728 } 729 730 @override 731 String get version { 732 if (_version == null) { 733 final String plistFile = fs.path.join(installPath, 'Contents', 'Info.plist'); 734 _version = PlistParser.instance.getValueFromFile( 735 plistFile, 736 PlistParser.kCFBundleShortVersionStringKey, 737 ) ?? 'unknown'; 738 } 739 return _version; 740 } 741 String _version; 742 743 @override 744 String get pluginsPath { 745 final List<String> split = version.split('.'); 746 final String major = split[0]; 747 final String minor = split[1]; 748 return fs.path.join(homeDirPath, 'Library', 'Application Support', '$id$major.$minor'); 749 } 750} 751 752class DeviceValidator extends DoctorValidator { 753 DeviceValidator() : super('Connected device'); 754 755 @override 756 String get slowWarning => 'Scanning for devices is taking a long time...'; 757 758 @override 759 Future<ValidationResult> validate() async { 760 final List<Device> devices = await deviceManager.getAllConnectedDevices().toList(); 761 List<ValidationMessage> messages; 762 if (devices.isEmpty) { 763 final List<String> diagnostics = await deviceManager.getDeviceDiagnostics(); 764 if (diagnostics.isNotEmpty) { 765 messages = diagnostics.map<ValidationMessage>((String message) => ValidationMessage(message)).toList(); 766 } else { 767 messages = <ValidationMessage>[ValidationMessage.hint(userMessages.devicesMissing)]; 768 } 769 } else { 770 messages = await Device.descriptions(devices) 771 .map<ValidationMessage>((String msg) => ValidationMessage(msg)).toList(); 772 } 773 774 if (devices.isEmpty) { 775 return ValidationResult(ValidationType.notAvailable, messages); 776 } else { 777 return ValidationResult(ValidationType.installed, messages, statusInfo: userMessages.devicesAvailable(devices.length)); 778 } 779 } 780} 781 782class ValidatorWithResult extends DoctorValidator { 783 ValidatorWithResult(String title, this.result) : super(title); 784 785 final ValidationResult result; 786 787 @override 788 Future<ValidationResult> validate() async => result; 789} 790