// 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. import 'dart:io'; import 'android/android_workflow.dart'; import 'base/context.dart'; import 'base/process.dart'; import 'globals.dart'; import 'ios/ios_workflow.dart'; // TODO(devoncarew): Make it easy to add version information to the `doctor` printout. class Doctor { Doctor() { _iosWorkflow = new IOSWorkflow(); if (_iosWorkflow.appliesToHostPlatform) _validators.add(_iosWorkflow); _androidWorkflow = new AndroidWorkflow(); if (_androidWorkflow.appliesToHostPlatform) _validators.add(_androidWorkflow); _validators.add(new _AtomValidator()); } 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; List<DoctorValidator> _validators = <DoctorValidator>[]; List<Workflow> get workflows { return new List<Workflow>.from(_validators.where((DoctorValidator validator) => validator is Workflow)); } /// 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; for (DoctorValidator validator in _validators) { ValidationResult result = validator.validate(); buffer.write('${result.leadingBox} The ${validator.label} is '); 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(); } /// 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(); } } bool get canListAnything => workflows.any((Workflow workflow) => workflow.canListDevices); 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 { /// 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; 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); } class ValidationResult { ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]); final ValidationType type; final Validator validator; final List<ValidationResult> childResults; String get leadingBox { if (type == ValidationType.missing) return '[ ]'; else if (type == ValidationType.installed) return '[✓]'; else return '[-]'; } void print([String indent = '']) { printSelf(indent); for (ValidationResult child in childResults) child.print(indent + ' '); } void printSelf([String indent = '']) { String result = indent; if (type == ValidationType.missing) result += '$leadingBox '; else if (type == ValidationType.installed) result += '$leadingBox '; else result += '$leadingBox '; 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}'); } List<ValidationResult> get _allResults { List<ValidationResult> results = <ValidationResult>[this]; results.addAll(childResults); return results; } } class _AtomValidator extends DoctorValidator { String get label => 'Atom development environment'; ValidationResult validate() { Validator atomValidator = new Validator( label, description: 'a lightweight development environment for Flutter' ); ValidationType atomExists() { return exitsHappy(<String>['atom', '--version']) ? ValidationType.installed : ValidationType.missing; }; ValidationType flutterPluginExists() { try { // apm list -b -p -i List<String> args = <String>['list', '-b', '-p', '-i']; printTrace('apm ${args.join(' ')}'); ProcessResult result = Process.runSync('apm', args); if (result.exitCode != 0) return ValidationType.missing; bool available = (result.stdout as String).split('\n').any((String line) { return line.startsWith('flutter@'); }); return available ? ValidationType.installed : ValidationType.missing; } catch (error) { return 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(); } void diagnose() => validate().print(); }