mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

...and various other minor cleanup: * Moved "FLUTTER_STORAGE_BASE_URL" into a constant throughout the code. There are other strings that we should do that to but this one was relevant to the code I was changing. * Fixed the logger's handling of slow warnings. Previously it deleted too much text. Fixed the test for that to actually verify it entirely, too. * Made the logger delete the slow warning when it's finished. * Fixed 'Please choose one (To quit, press "q/Q")' message to be the cleaner 'Please choose one (or "q" to quit)'. * Added a debug toString to ValidationResult for debugging purposes (not used). * In http_host_validator: - Shortened constant names to be clearer (e.g. kPubDevHttpHost -> kPubDev). - Added GitHub as a tested host since when you run `flutter` we hit that immediately. - Renamed the check "Network resources". - Updated the `slowWarning` of the check to say which hosts are pending. - Removed all timeout logic. Timeouts violate our style guide. - Removed `int.parse(... ?? '10')`; passing a constant to `int.parse` is inefficient. - Replaced the `_HostValidationResult` class with `String?` for simplicity. - Improved the error messages to be more detailed. - Removed all checks that dependened on the stringification of exceptions. That's very brittle. - Added a warning specifically for HandshakeException that talks about the implications (MITM attacks). - Replaced exception-message-parsing logic with just calling `Uri.tryParse` and validating the result. - Replaced a lot of list-filtering logic with just a single for loop to check the results. - Replaced code that added a constant to a known-empty list with just returning a constant list. - Revamped the logic for deciding which hosts to check to just use a single chain of if/else blocks instead of getters, lists literals with `if` expressions, `??`, functions, etc spread over multiple places in the code.
318 lines
10 KiB
Dart
318 lines
10 KiB
Dart
// Copyright 2014 The Flutter 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 'package:meta/meta.dart';
|
|
|
|
import 'base/async_guard.dart';
|
|
import 'base/terminal.dart';
|
|
import 'globals.dart' as globals;
|
|
|
|
class ValidatorTask {
|
|
ValidatorTask(this.validator, this.result);
|
|
final DoctorValidator validator;
|
|
final Future<ValidationResult> result;
|
|
}
|
|
|
|
/// A series of tools and required install steps for a target platform (iOS or Android).
|
|
abstract class Workflow {
|
|
const Workflow();
|
|
|
|
/// 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;
|
|
|
|
/// Are we functional enough to list emulators?
|
|
bool get canListEmulators;
|
|
}
|
|
|
|
enum ValidationType {
|
|
crash,
|
|
missing,
|
|
partial,
|
|
notAvailable,
|
|
success,
|
|
}
|
|
|
|
enum ValidationMessageType {
|
|
error,
|
|
hint,
|
|
information,
|
|
}
|
|
|
|
abstract class DoctorValidator {
|
|
const DoctorValidator(this.title);
|
|
|
|
/// This is displayed in the CLI.
|
|
final String title;
|
|
|
|
String get slowWarning => 'This is taking an unexpectedly long time...';
|
|
|
|
static const Duration _slowWarningDuration = Duration(seconds: 10);
|
|
|
|
/// Duration before the spinner should display [slowWarning].
|
|
Duration get slowWarningDuration => _slowWarningDuration;
|
|
|
|
Future<ValidationResult> validate();
|
|
}
|
|
|
|
/// 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 {
|
|
GroupedValidator(this.subValidators) : super(subValidators[0].title);
|
|
|
|
final List<DoctorValidator> subValidators;
|
|
|
|
List<ValidationResult> _subResults = <ValidationResult>[];
|
|
|
|
/// Sub-validator results.
|
|
///
|
|
/// To avoid losing information when results are merged, the sub-results are
|
|
/// cached on this field when they are available. The results are in the same
|
|
/// order as the sub-validator list.
|
|
List<ValidationResult> get subResults => _subResults;
|
|
|
|
@override
|
|
String get slowWarning => _currentSlowWarning;
|
|
String _currentSlowWarning = 'Initializing...';
|
|
|
|
@override
|
|
Future<ValidationResult> validate() async {
|
|
final List<ValidatorTask> tasks = <ValidatorTask>[
|
|
for (final DoctorValidator validator in subValidators)
|
|
ValidatorTask(
|
|
validator,
|
|
asyncGuard<ValidationResult>(() => validator.validate()),
|
|
),
|
|
];
|
|
|
|
final List<ValidationResult> results = <ValidationResult>[];
|
|
for (final ValidatorTask subValidator in tasks) {
|
|
_currentSlowWarning = subValidator.validator.slowWarning;
|
|
try {
|
|
results.add(await subValidator.result);
|
|
} on Exception catch (exception, stackTrace) {
|
|
results.add(ValidationResult.crash(exception, stackTrace));
|
|
}
|
|
}
|
|
_currentSlowWarning = 'Merging results...';
|
|
return _mergeValidationResults(results);
|
|
}
|
|
|
|
ValidationResult _mergeValidationResults(List<ValidationResult> results) {
|
|
assert(results.isNotEmpty, 'Validation results should not be empty');
|
|
_subResults = results;
|
|
ValidationType mergedType = results[0].type;
|
|
final List<ValidationMessage> mergedMessages = <ValidationMessage>[];
|
|
String? statusInfo;
|
|
|
|
for (final ValidationResult result in results) {
|
|
statusInfo ??= result.statusInfo;
|
|
switch (result.type) {
|
|
case ValidationType.success:
|
|
if (mergedType == ValidationType.missing) {
|
|
mergedType = ValidationType.partial;
|
|
}
|
|
break;
|
|
case ValidationType.notAvailable:
|
|
case ValidationType.partial:
|
|
mergedType = ValidationType.partial;
|
|
break;
|
|
case ValidationType.crash:
|
|
case ValidationType.missing:
|
|
if (mergedType == ValidationType.success) {
|
|
mergedType = ValidationType.partial;
|
|
}
|
|
break;
|
|
}
|
|
mergedMessages.addAll(result.messages);
|
|
}
|
|
|
|
return ValidationResult(mergedType, mergedMessages,
|
|
statusInfo: statusInfo);
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class ValidationResult {
|
|
/// [ValidationResult.type] should only equal [ValidationResult.success]
|
|
/// if no [messages] are hints or errors.
|
|
const ValidationResult(this.type, this.messages, { this.statusInfo });
|
|
|
|
factory ValidationResult.crash(Object error, [StackTrace? stackTrace]) {
|
|
return ValidationResult(ValidationType.crash, <ValidationMessage>[
|
|
const ValidationMessage.error(
|
|
'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.'),
|
|
ValidationMessage.error('$error'),
|
|
if (stackTrace != null)
|
|
// Stacktrace is informational. Printed in verbose mode only.
|
|
ValidationMessage('$stackTrace'),
|
|
], statusInfo: 'the doctor check crashed');
|
|
}
|
|
|
|
final ValidationType type;
|
|
// A short message about the status.
|
|
final String? statusInfo;
|
|
final List<ValidationMessage> messages;
|
|
|
|
String get leadingBox {
|
|
switch (type) {
|
|
case ValidationType.crash:
|
|
return '[☠]';
|
|
case ValidationType.missing:
|
|
return '[✗]';
|
|
case ValidationType.success:
|
|
return '[✓]';
|
|
case ValidationType.notAvailable:
|
|
case ValidationType.partial:
|
|
return '[!]';
|
|
}
|
|
}
|
|
|
|
String get coloredLeadingBox {
|
|
switch (type) {
|
|
case ValidationType.crash:
|
|
return globals.terminal.color(leadingBox, TerminalColor.red);
|
|
case ValidationType.missing:
|
|
return globals.terminal.color(leadingBox, TerminalColor.red);
|
|
case ValidationType.success:
|
|
return globals.terminal.color(leadingBox, TerminalColor.green);
|
|
case ValidationType.notAvailable:
|
|
case ValidationType.partial:
|
|
return globals.terminal.color(leadingBox, TerminalColor.yellow);
|
|
}
|
|
}
|
|
|
|
/// The string representation of the type.
|
|
String get typeStr {
|
|
switch (type) {
|
|
case ValidationType.crash:
|
|
return 'crash';
|
|
case ValidationType.missing:
|
|
return 'missing';
|
|
case ValidationType.success:
|
|
return 'installed';
|
|
case ValidationType.notAvailable:
|
|
return 'notAvailable';
|
|
case ValidationType.partial:
|
|
return 'partial';
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return '$runtimeType($type, $messages, $statusInfo)';
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
@immutable
|
|
class ValidationMessage {
|
|
/// Create a validation message with information for a passing validator.
|
|
///
|
|
/// 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, String? piiStrippedMessage })
|
|
: type = ValidationMessageType.information, piiStrippedMessage = piiStrippedMessage ?? message;
|
|
|
|
/// Create a validation message with information for a failing validator.
|
|
const ValidationMessage.error(this.message, { String? piiStrippedMessage })
|
|
: type = ValidationMessageType.error,
|
|
piiStrippedMessage = piiStrippedMessage ?? message,
|
|
contextUrl = null;
|
|
|
|
/// Create a validation message with information for a partially failing
|
|
/// validator.
|
|
const ValidationMessage.hint(this.message, { String? piiStrippedMessage })
|
|
: type = ValidationMessageType.hint,
|
|
piiStrippedMessage = piiStrippedMessage ?? message,
|
|
contextUrl = null;
|
|
|
|
final ValidationMessageType type;
|
|
final String? contextUrl;
|
|
final String message;
|
|
/// Optional message with PII stripped, to show instead of [message].
|
|
final String piiStrippedMessage;
|
|
|
|
bool get isError => type == ValidationMessageType.error;
|
|
|
|
bool get isHint => type == ValidationMessageType.hint;
|
|
|
|
bool get isInformation => type == ValidationMessageType.information;
|
|
|
|
String get indicator {
|
|
switch (type) {
|
|
case ValidationMessageType.error:
|
|
return '✗';
|
|
case ValidationMessageType.hint:
|
|
return '!';
|
|
case ValidationMessageType.information:
|
|
return '•';
|
|
}
|
|
}
|
|
|
|
String get coloredIndicator {
|
|
switch (type) {
|
|
case ValidationMessageType.error:
|
|
return globals.terminal.color(indicator, TerminalColor.red);
|
|
case ValidationMessageType.hint:
|
|
return globals.terminal.color(indicator, TerminalColor.yellow);
|
|
case ValidationMessageType.information:
|
|
return globals.terminal.color(indicator, TerminalColor.green);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() => message;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
return other is ValidationMessage
|
|
&& other.message == message
|
|
&& other.type == type
|
|
&& other.contextUrl == contextUrl;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => Object.hash(type, message, contextUrl);
|
|
}
|
|
|
|
class NoIdeValidator extends DoctorValidator {
|
|
NoIdeValidator() : super('Flutter IDE Support');
|
|
|
|
@override
|
|
Future<ValidationResult> validate() async {
|
|
return ValidationResult(
|
|
// Info hint to user they do not have a supported IDE installed
|
|
ValidationType.notAvailable,
|
|
globals.userMessages.noIdeInstallationInfo.map((String ideInfo) => ValidationMessage(ideInfo)).toList(),
|
|
statusInfo: globals.userMessages.noIdeStatusInfo,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ValidatorWithResult extends DoctorValidator {
|
|
ValidatorWithResult(super.title, this.result);
|
|
|
|
final ValidationResult result;
|
|
|
|
@override
|
|
Future<ValidationResult> validate() async => result;
|
|
}
|