Commit 25f332d8 authored by Devon Carew's avatar Devon Carew

re-work the doctor command

parent 1db09ffe
......@@ -102,20 +102,16 @@ class AndroidSdk {
String get adbPath => getPlatformToolsPath('adb');
bool validateSdkWellFormed({ bool complain: false }) {
if (!FileSystemEntity.isFileSync(adbPath)) {
if (complain)
printError('Android SDK file not found: $adbPath.');
return false;
}
/// Validate the Android SDK. This returns an empty list if there are no
/// issues; otherwise, it returns a list of issues found.
List<String> validateSdkWellFormed() {
if (!FileSystemEntity.isFileSync(adbPath))
return <String>['Android SDK file not found: $adbPath.'];
if (sdkVersions.isEmpty) {
if (complain)
printError('Android SDK does not have the proper build-tools.');
return false;
}
if (sdkVersions.isEmpty || latestVersion == null)
return <String>['Android SDK does not have the proper build-tools.'];
return latestVersion.validateSdkWellFormed(complain: complain);
return latestVersion.validateSdkWellFormed();
}
String getPlatformToolsPath(String binaryName) {
......@@ -215,12 +211,20 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
String get zipalignPath => getBuildToolsPath('zipalign');
bool validateSdkWellFormed({ bool complain: false }) {
return
_exists(androidJarPath, complain: complain) &&
_exists(aaptPath, complain: complain) &&
_exists(dxPath, complain: complain) &&
_exists(zipalignPath, complain: complain);
List<String> validateSdkWellFormed() {
if (_exists(androidJarPath) != null)
return <String>[_exists(androidJarPath)];
if (_exists(aaptPath) != null)
return <String>[_exists(aaptPath)];
if (_exists(dxPath) != null)
return <String>[_exists(dxPath)];
if (_exists(zipalignPath) != null)
return <String>[_exists(zipalignPath)];
return <String>[];
}
String getPlatformsPath(String itemName) {
......@@ -237,13 +241,9 @@ class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
@override
String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]';
bool _exists(String path, { bool complain: false }) {
if (!FileSystemEntity.isFileSync(path)) {
if (complain)
printError('Android SDK file not found: $path.');
return false;
}
return true;
String _exists(String path) {
if (!FileSystemEntity.isFileSync(path))
return 'Android SDK file not found: $path.';
return null;
}
}
......@@ -6,9 +6,8 @@ import '../doctor.dart';
import '../globals.dart';
import 'android_sdk.dart';
class AndroidWorkflow extends Workflow {
@override
String get label => 'Android toolchain';
class AndroidWorkflow extends DoctorValidator implements Workflow {
AndroidWorkflow() : super('Android toolchain - develop for Android devices');
@override
bool get appliesToHostPlatform => true;
......@@ -17,30 +16,43 @@ class AndroidWorkflow extends Workflow {
bool get canListDevices => getAdbPath(androidSdk) != null;
@override
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed(complain: false);
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
@override
ValidationResult validate() {
Validator androidValidator = new Validator(
label,
description: 'develop for Android devices'
);
ValidationType sdkExists() {
return androidSdk == null ? ValidationType.missing : ValidationType.installed;
};
androidValidator.addValidator(new Validator(
'Android Studio / Android SDK',
description: 'enable development for Android devices',
resolution: 'Download from https://developer.android.com/sdk/ (or visit '
'https://flutter.io/setup/#android-setup for detailed instructions)',
validatorFunction: sdkExists
List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing;
String sdkVersionText;
if (androidSdk == null) {
messages.add(new ValidationMessage.error(
'Android Studio / Android SDK not found. Download from https://developer.android.com/sdk/\n'
'(or visit https://flutter.io/setup/#android-setup for detailed instructions).'
));
} else {
type = ValidationType.partial;
messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
if (androidSdk.latestVersion != null) {
sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
return androidValidator.validate();
messages.add(new ValidationMessage('Platform ${androidSdk.latestVersion.platformVersionName}'));
messages.add(new ValidationMessage('Build-tools ${androidSdk.latestVersion.buildToolsVersionName}'));
}
@override
void diagnose() => validate().print();
List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isEmpty) {
type = ValidationType.installed;
} else {
messages.addAll(validationResult.map((String message) {
return new ValidationMessage.error(message);
}));
messages.add(new ValidationMessage('Try re-installing or updating your Android SDK.'));
}
}
return new ValidationResult(type, messages, statusInfo: sdkVersionText);
}
}
......@@ -178,7 +178,9 @@ class BuildApkCommand extends FlutterCommand {
return 1;
}
if (!androidSdk.validateSdkWellFormed(complain: true)) {
List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.');
return 1;
}
......@@ -375,7 +377,9 @@ Future<int> buildAndroid({
return 1;
}
if (!androidSdk.validateSdkWellFormed(complain: true)) {
List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) {
validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.');
return 1;
}
......
......@@ -4,10 +4,8 @@
import 'dart:async';
import '../artifacts.dart';
import '../globals.dart';
import '../runner/flutter_command.dart';
import '../runner/version.dart';
class DoctorCommand extends FlutterCommand {
@override
......@@ -21,18 +19,8 @@ class DoctorCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
// general info
String flutterRoot = ArtifactStore.flutterRoot;
printStatus('Flutter root: $flutterRoot.');
printStatus('');
// doctor
doctor.diagnose();
printStatus('');
// version
printStatus(getVersion(flutterRoot));
return 0;
}
}
......@@ -22,7 +22,7 @@ class UpgradeCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
try {
runCheckedSync(<String>[
......@@ -50,7 +50,7 @@ class UpgradeCommand extends FlutterCommand {
return code;
printStatus('');
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
return 0;
}
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show JSON;
import 'dart:io';
import 'package:path/path.dart' as path;
......@@ -11,19 +12,20 @@ import 'base/context.dart';
import 'base/os.dart';
import 'globals.dart';
import 'ios/ios_workflow.dart';
// TODO(devoncarew): Make it easy to add version information to the `doctor` printout.
import 'runner/version.dart';
class Doctor {
Doctor() {
_iosWorkflow = new IOSWorkflow();
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
_validators.add(new _FlutterValidator());
_androidWorkflow = new AndroidWorkflow();
if (_androidWorkflow.appliesToHostPlatform)
_validators.add(_androidWorkflow);
_iosWorkflow = new IOSWorkflow();
if (_iosWorkflow.appliesToHostPlatform)
_validators.add(_iosWorkflow);
_validators.add(new _AtomValidator());
}
......@@ -55,13 +57,19 @@ class Doctor {
for (DoctorValidator validator in _validators) {
ValidationResult result = validator.validate();
buffer.write('${result.leadingBox} The ${validator.label} is ');
buffer.write('${result.leadingBox} ${validator.title} is ');
if (result.type == ValidationType.missing)
buffer.writeln('not installed.');
buffer.write('not installed.');
else if (result.type == ValidationType.partial)
buffer.writeln('partially installed; more components are available.');
buffer.write('partially installed; more components are available.');
else
buffer.writeln('fully installed.');
buffer.write('fully installed.');
if (result.statusInfo != null)
buffer.write(' (${result.statusInfo})');
buffer.writeln();
if (result.type != ValidationType.installed)
allGood = false;
}
......@@ -77,11 +85,26 @@ class Doctor {
/// 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();
ValidationResult result = validator.validate();
if (result.statusInfo != null)
printStatus('${result.leadingBox} ${validator.title} (${result.statusInfo})');
else
printStatus('${result.leadingBox} ${validator.title}');
for (ValidationMessage message in result.messages) {
if (message.isError) {
printStatus(' x ${message.message.replaceAll('\n', '\n ')}');
} else {
printStatus(' • ${message.message.replaceAll('\n', '\n ')}');
}
}
}
}
......@@ -90,17 +113,8 @@ class Doctor {
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 {
abstract class Workflow {
/// Whether the workflow applies to this platform (as in, should we ever try and use it).
bool get appliesToHostPlatform;
......@@ -117,57 +131,21 @@ enum ValidationType {
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);
}
abstract class DoctorValidator {
DoctorValidator(this.title);
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;
}
final String title;
void addValidator(Validator validator) => _children.add(validator);
ValidationResult validate();
}
class ValidationResult {
ValidationResult(this.type, this.validator, [this.childResults = const <ValidationResult>[]]);
ValidationResult(this.type, this.messages, { this.statusInfo });
final ValidationType type;
final Validator validator;
final List<ValidationResult> childResults;
// A short message about the status.
final String statusInfo;
final List<ValidationMessage> messages;
String get leadingBox {
if (type == ValidationType.missing)
......@@ -177,49 +155,42 @@ class ValidationResult {
else
return '[-]';
}
}
void print([String indent = '']) {
printSelf(indent);
for (ValidationResult child in childResults)
child.print(indent + ' ');
}
void printSelf([String indent = '']) {
String result = indent;
class ValidationMessage {
ValidationMessage(this.message) : isError = false;
ValidationMessage.error(this.message) : isError = true;
if (type == ValidationType.missing)
result += '$leadingBox ';
else if (type == ValidationType.installed)
result += '$leadingBox ';
else
result += '$leadingBox ';
final bool isError;
final String message;
result += '${validator.name} ';
@override
String toString() => message;
}
if (validator.description != null)
result += '- ${validator.description} ';
class _FlutterValidator extends DoctorValidator {
_FlutterValidator() : super('Flutter');
if (type == ValidationType.missing)
result += '(missing)';
else if (type == ValidationType.installed)
result += '(installed)';
@override
ValidationResult validate() {
List<ValidationMessage> messages = <ValidationMessage>[];
printStatus(result);
FlutterVersion version = FlutterVersion.getVersion();
if (type == ValidationType.missing && validator.resolution != null)
printStatus('$indent ${validator.resolution}');
}
messages.add(new ValidationMessage('Flutter root at ${version.flutterRoot}'));
messages.add(new ValidationMessage('Framework revision ${version.frameworkRevisionShort} '
'(${version.frameworkAge})'));
messages.add(new ValidationMessage('Engine revision ${version.engineRevisionShort}'));
List<ValidationResult> get _allResults {
List<ValidationResult> results = <ValidationResult>[this];
results.addAll(childResults);
return results;
return new ValidationResult(ValidationType.installed, messages,
statusInfo: '${version.frameworkRevisionShort} - channel ${version.channel}');
}
}
class _AtomValidator extends DoctorValidator {
static String getAtomHomePath() {
_AtomValidator() : super('Atom - a lightweight development environment for Flutter');
static String _getAtomHomePath() {
final Map<String, String> env = Platform.environment;
if (env['ATOM_HOME'] != null)
return env['ATOM_HOME'];
......@@ -228,43 +199,43 @@ class _AtomValidator extends DoctorValidator {
: path.join(env['HOME'], '.atom');
}
@override
String get label => 'Atom development environment';
@override
ValidationResult validate() {
Validator atomValidator = new Validator(
label,
description: 'a lightweight development environment for Flutter'
);
List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType atomExists() {
bool atomDirExists = FileSystemEntity.isDirectorySync(getAtomHomePath());
return atomDirExists ? ValidationType.installed : ValidationType.missing;
};
ValidationType flutterPluginExists() {
String flutterPluginPath = path.join(getAtomHomePath(), 'packages', 'flutter');
bool flutterPluginExists = FileSystemEntity.isDirectorySync(flutterPluginPath);
return flutterPluginExists ? ValidationType.installed : ValidationType.missing;
};
atomValidator.addValidator(new Validator(
'Atom editor',
resolution: 'Download at https://atom.io',
validatorFunction: atomExists
int installCount = 0;
bool atomDirExists = FileSystemEntity.isDirectorySync(_getAtomHomePath());
if (!atomDirExists) {
messages.add(new ValidationMessage.error(
'Atom not installed; download at https://atom.io.'
));
} else {
installCount++;
}
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
String flutterPluginPath = path.join(_getAtomHomePath(), 'packages', 'flutter');
if (!FileSystemEntity.isDirectorySync(flutterPluginPath)) {
messages.add(new ValidationMessage.error(
'Flutter plugin not installed; this adds Flutter specific functionality to Atom.\n'
'Install the \'flutter\' plugin in Atom or run \'apm install flutter\'.'
));
} else {
installCount++;
return atomValidator.validate();
try {
File packageFile = new File(path.join(flutterPluginPath, 'package.json'));
dynamic packageInfo = JSON.decode(packageFile.readAsStringSync());
String version = packageInfo['version'];
messages.add(new ValidationMessage('Atom installed; flutter plugin version $version'));
} catch (error) {
printTrace('Unable to read flutter plugin version: $error');
}
}
@override
void diagnose() => validate().print();
return new ValidationResult(
installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing,
messages
);
}
}
......@@ -79,7 +79,7 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false;
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
if (!doctor.iosWorkflow.hasIdeviceId)
if (!doctor.iosWorkflow.hasIDeviceId)
return <IOSDevice>[];
List<IOSDevice> devices = [];
......
......@@ -8,112 +8,96 @@ import '../base/process.dart';
import '../doctor.dart';
import 'mac.dart';
class IOSWorkflow extends Workflow {
@override
String get label => 'iOS toolchain';
XCode get xcode => XCode.instance;
class IOSWorkflow extends DoctorValidator implements Workflow {
IOSWorkflow() : super('iOS toolchain - develop for iOS devices');
@override
bool get appliesToHostPlatform => Platform.isMacOS;
// We need xcode (+simctl) to list simulator devices, and idevice_id to list real devices.
@override
bool get canListDevices => XCode.instance.isInstalledAndMeetsVersionCheck;
bool get canListDevices => xcode.isInstalledAndMeetsVersionCheck;
// We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy
// for real devices.
@override
bool get canLaunchDevices => XCode.instance.isInstalledAndMeetsVersionCheck;
bool get canLaunchDevices => xcode.isInstalledAndMeetsVersionCheck;
bool get hasIDeviceId => exitsHappy(<String>['idevice_id', '-h']);
@override
ValidationResult validate() {
Validator iosValidator = new Validator(
label,
description: 'develop for iOS devices'
);
List<ValidationMessage> messages = <ValidationMessage>[];
int installCount = 0;
String xcodeVersionInfo;
ValidationType xcodeExists() {
return XCode.instance.isInstalled ? ValidationType.installed : ValidationType.missing;
};
ValidationType xcodeVersionSatisfactory() {
return XCode.instance.isInstalledAndMeetsVersionCheck ? ValidationType.installed : ValidationType.missing;
};
ValidationType xcodeEulaSigned() {
return XCode.instance.eulaSigned ? ValidationType.installed : ValidationType.missing;
};
ValidationType brewExists() {
return exitsHappy(<String>['brew', '-v'])
? ValidationType.installed : ValidationType.missing;
};
ValidationType ideviceinstallerExists() {
return exitsHappy(<String>['ideviceinstaller', '-h'])
? ValidationType.installed : ValidationType.missing;
};
ValidationType iosdeployExists() {
return hasIdeviceId ? ValidationType.installed : ValidationType.missing;
};
Validator xcodeValidator = new Validator(
'XCode',
description: 'enable development for iOS devices',
resolution: 'Download at https://developer.apple.com/xcode/download/',
validatorFunction: xcodeExists
);
if (xcode.isInstalled) {
installCount++;
iosValidator.addValidator(xcodeValidator);
xcodeVersionInfo = xcode.xcodeVersionText;
if (xcodeVersionInfo.contains(','))
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
xcodeValidator.addValidator(new Validator(
'version',
description: 'Xcode minimum version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0',
resolution: 'Download the latest version or update via the Mac App Store',
validatorFunction: xcodeVersionSatisfactory
messages.add(new ValidationMessage(xcode.xcodeVersionText));
if (!xcode.isInstalledAndMeetsVersionCheck) {
messages.add(new ValidationMessage.error(
'Flutter requires a minimum XCode version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0.\n'
'Download the latest version or update via the Mac App Store.'
));
}
xcodeValidator.addValidator(new Validator(
'EULA',
description: 'XCode end user license agreement',
resolution: "Open XCode or run the command 'sudo xcodebuild -license'",
validatorFunction: xcodeEulaSigned
if (!xcode.eulaSigned) {
messages.add(new ValidationMessage.error(
'XCode end user license agreement not signed; open XCode or run the command \'sudo xcodebuild -license\'.'
));
}
} else {
messages.add(new ValidationMessage.error(
'XCode not installed; this is necessary for iOS development.\n'
'Download at https://developer.apple.com/xcode/download/.'
));
}
Validator brewValidator = new Validator(
'brew',
description: 'install additional development packages',
resolution: 'Download at http://brew.sh/',
validatorFunction: brewExists
);
// brew installed
if (exitsHappy(<String>['brew', '-v'])) {
installCount++;
iosValidator.addValidator(brewValidator);
List<String> installed = <String>[];
brewValidator.addValidator(new Validator(
'ideviceinstaller',
description: 'discover connected iOS devices',
resolution: "Install via 'brew install ideviceinstaller'",
validatorFunction: ideviceinstallerExists
if (!exitsHappy(<String>['ideviceinstaller', '-h'])) {
messages.add(new ValidationMessage.error(
'ideviceinstaller not available; this is used to discover connected iOS devices.\n'
'Install via \'brew install ideviceinstaller\'.'
));
} else {
installed.add('ideviceinstaller');
}
brewValidator.addValidator(new Validator(
'ios-deploy',
description: 'deploy to connected iOS devices',
resolution: "Install via 'brew install ios-deploy'",
validatorFunction: iosdeployExists
if (!hasIDeviceId) {
messages.add(new ValidationMessage.error(
'ios-deploy not available; this is used to deploy to connected iOS devices.\n'
'Install via \'brew install ios-deploy\'.'
));
return iosValidator.validate();
} else {
installed.add('ios-deploy');
}
@override
void diagnose() => validate().print();
bool get hasIdeviceId => exitsHappy(<String>['idevice_id', '-h']);
if (installed.isNotEmpty)
messages.add(new ValidationMessage(installed.join(', ') + ' installed'));
} else {
messages.add(new ValidationMessage.error(
'Brew not installed; use this to install tools for iOS device development.\n'
'Download brew at http://brew.sh/.'
));
}
/// Return whether the tooling to list and deploy to real iOS devices (not the
/// simulator) is installed on the user's machine.
bool get canWorkWithIOSDevices {
return exitsHappy(<String>['ideviceinstaller', '-h']) && hasIdeviceId;
return new ValidationResult(
installCount == 2 ? ValidationType.installed : installCount == 1 ? ValidationType.partial : ValidationType.missing,
messages,
statusInfo: xcodeVersionInfo
);
}
}
......@@ -54,27 +54,24 @@ class XCode {
}
}
bool _xcodeVersionSatisfactory;
bool get xcodeVersionSatisfactory {
if (_xcodeVersionSatisfactory != null)
return _xcodeVersionSatisfactory;
String _xcodeVersionText;
try {
String output = runSync(<String>['xcodebuild', '-version']);
String get xcodeVersionText {
if (_xcodeVersionText == null)
_xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', ');
return _xcodeVersionText;
}
bool get xcodeVersionSatisfactory {
RegExp regex = new RegExp(r'Xcode ([0-9.]+)');
String version = regex.firstMatch(output).group(1);
String version = regex.firstMatch(xcodeVersionText).group(1);
List<String> components = version.split('.');
int major = int.parse(components[0]);
int minor = components.length == 1 ? 0 : int.parse(components[1]);
_xcodeVersionSatisfactory = _xcodeVersionCheckValid(major, minor);
} catch (error) {
_xcodeVersionSatisfactory = false;
}
return _xcodeVersionSatisfactory;
return _xcodeVersionCheckValid(major, minor);
}
}
......
......@@ -200,16 +200,8 @@ class FlutterCommandRunner extends CommandRunner {
}
}
if (androidSdk != null) {
printTrace('Using Android SDK at ${androidSdk.directory}.');
if (androidSdk.latestVersion != null)
printTrace('${androidSdk.latestVersion}');
}
if (globalResults['version']) {
printStatus(getVersion(ArtifactStore.flutterRoot));
printStatus('');
doctor.summary();
printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
return new Future<int>.value(0);
}
......
......@@ -5,26 +5,54 @@
import '../artifacts.dart';
import '../base/process.dart';
String getVersion(String flutterRoot) {
String upstream = runSync([
'git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}'
], workingDirectory: flutterRoot).trim();
String repository;
int slash = upstream.indexOf('/');
class FlutterVersion {
FlutterVersion(this.flutterRoot) {
_channel = _runGit('git rev-parse --abbrev-ref --symbolic @{u}');
int slash = _channel.indexOf('/');
if (slash != -1) {
String remote = upstream.substring(0, slash);
repository = runSync([
'git', 'ls-remote', '--get-url', remote
], workingDirectory: flutterRoot).trim();
upstream = upstream.substring(slash + 1);
String remote = _channel.substring(0, slash);
_repositoryUrl = _runGit('git ls-remote --get-url $remote');
_channel = _channel.substring(slash + 1);
} else if (_channel.isEmpty) {
_channel = 'unknown';
}
_frameworkRevision = _runGit('git log -n 1 --pretty=format:%H');
_frameworkAge = _runGit('git log -n 1 --pretty=format:%ar');
}
String revision = runSync([
'git', 'log', '-n', '1', '--pretty=format:%H (%ar)'
], workingDirectory: flutterRoot).trim();
String from = repository == null ? 'Flutter from unknown source' : 'Flutter from $repository (on $upstream)';
String flutterVersion = 'Framework: $revision';
String engineRevision = 'Engine: ${ArtifactStore.engineRevision}';
final String flutterRoot;
String _repositoryUrl;
String get repositoryUrl => _repositoryUrl;
String _channel;
/// `master`, `alpha`, `hackathon`, ...
String get channel => _channel;
String _frameworkRevision;
String get frameworkRevision => _frameworkRevision;
String get frameworkRevisionShort => _runGit('git rev-parse --short $frameworkRevision');
String _frameworkAge;
String get frameworkAge => _frameworkAge;
return '$from\n$flutterVersion\n$engineRevision';
String get engineRevision => ArtifactStore.engineRevision;
String get engineRevisionShort => _runGit('git rev-parse --short $engineRevision');
String _runGit(String command) => runSync(command.split(' '), workingDirectory: flutterRoot);
@override
String toString() {
String from = repositoryUrl == null ? 'Flutter from unknown source' : 'Flutter from $repositoryUrl (on channel $channel)';
String flutterText = 'Framework: $frameworkRevisionShort ($frameworkAge)';
String engineText = 'Engine: $engineRevisionShort';
return '$from\n\n$flutterText\n$engineText';
}
static FlutterVersion getVersion([String flutterRoot]) {
return new FlutterVersion(flutterRoot != null ? flutterRoot : ArtifactStore.flutterRoot);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment