Commit 8ccc9a45 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by Todd Volkert

Add flutter doctor support for VS Code (#14463)

Looks in default install locations on Mac, Linux and Windows for VS Code. If found, looks in default extension location to see if Dart Code is installed.

If VS Code is not installed, nothing is reported. If VS Code is installed without Dart Code, a warning is shown.
parent 788f01f9
...@@ -23,6 +23,7 @@ import 'globals.dart'; ...@@ -23,6 +23,7 @@ import 'globals.dart';
import 'ios/ios_workflow.dart'; import 'ios/ios_workflow.dart';
import 'ios/plist_utils.dart'; import 'ios/plist_utils.dart';
import 'version.dart'; import 'version.dart';
import 'vscode/vscode_validator.dart';
Doctor get doctor => context[Doctor]; Doctor get doctor => context[Doctor];
...@@ -43,6 +44,7 @@ class Doctor { ...@@ -43,6 +44,7 @@ class Doctor {
final List<DoctorValidator> ideValidators = <DoctorValidator>[]; final List<DoctorValidator> ideValidators = <DoctorValidator>[];
ideValidators.addAll(AndroidStudioValidator.allValidators); ideValidators.addAll(AndroidStudioValidator.allValidators);
ideValidators.addAll(IntelliJValidator.installedValidators); ideValidators.addAll(IntelliJValidator.installedValidators);
ideValidators.addAll(VsCodeValidator.installedValidators);
if (ideValidators.isNotEmpty) if (ideValidators.isNotEmpty)
_validators.addAll(ideValidators); _validators.addAll(ideValidators);
else else
......
// Copyright 2018 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:convert';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/platform.dart';
import '../base/version.dart';
// Include VS Code insiders (useful for debugging).
const bool _includeInsiders = false;
class VsCode {
static const String extensionIdentifier = 'Dart-Code.dart-code';
VsCode._(this.directory, this.extensionDirectory, { Version version })
: this.version = version ?? Version.unknown {
if (!fs.isDirectorySync(directory)) {
_validationMessages.add('VS Code not found at $directory');
return;
}
// If the extensions directory doesn't exist at all, the listSync()
// below will fail, so just bail out early.
if (!fs.isDirectorySync(extensionDirectory)) {
return;
}
// Check for presence of extension.
final Iterable<FileSystemEntity> extensionDirs = fs
.directory(extensionDirectory)
.listSync()
.where((FileSystemEntity d) => d is Directory)
.where(
(FileSystemEntity d) => d.basename.startsWith(extensionIdentifier));
if (extensionDirs.isNotEmpty) {
final FileSystemEntity extensionDir = extensionDirs.first;
_isValid = true;
_extensionVersion = new Version.parse(
extensionDir.basename.substring('$extensionIdentifier-'.length));
_validationMessages.add('Dart Code extension version $_extensionVersion');
}
}
final String directory;
final String extensionDirectory;
final Version version;
bool _isValid = false;
Version _extensionVersion;
final List<String> _validationMessages = <String>[];
factory VsCode.fromDirectory(String installPath, String extensionDirectory) {
final String packageJsonPath =
fs.path.join(installPath, 'resources', 'app', 'package.json');
final String versionString = _getVersionFromPackageJson(packageJsonPath);
Version version;
if (versionString != null)
version = new Version.parse(versionString);
return new VsCode._(installPath, extensionDirectory, version: version);
}
bool get isValid => _isValid;
Iterable<String> get validationMessages => _validationMessages;
static List<VsCode> allInstalled() {
if (platform.isMacOS)
return _installedMacOS();
else if (platform.isWindows)
return _installedWindows();
else if (platform.isLinux)
return _installedLinux();
else
// VS Code isn't supported on the other platforms.
return <VsCode>[];
}
// macOS:
// /Applications/Visual Studio Code.app/Contents/
// /Applications/Visual Studio Code - Insiders.app/Contents/
// $HOME/Applications/Visual Studio Code.app/Contents/
// $HOME/Applications/Visual Studio Code - Insiders.app/Contents/
// macOS Extensions:
// $HOME/.vscode/extensions
// $HOME/.vscode-insiders/extensions
static List<VsCode> _installedMacOS() {
final Map<String, String> stable = <String, String>{
fs.path.join('/Applications', 'Visual Studio Code.app', 'Contents'):
'.vscode',
fs.path.join(homeDirPath, 'Applications', 'Visual Studio Code.app',
'Contents'): '.vscode'
};
final Map<String, String> insiders = <String, String>{
fs.path.join(
'/Applications', 'Visual Studio Code - Insiders.app', 'Contents'):
'.vscode-insiders',
fs.path.join(homeDirPath, 'Applications',
'Visual Studio Code - Insiders.app', 'Contents'): '.vscode-insiders'
};
return _findInstalled(stable, insiders);
}
// Windows:
// $programfiles(x86)\Microsoft VS Code
// $programfiles(x86)\Microsoft VS Code Insiders
// TODO: Confirm these are correct for 64bit
// $programfiles\Microsoft VS Code
// $programfiles\Microsoft VS Code Insiders
// Windows Extensions:
// $HOME/.vscode/extensions
// $HOME/.vscode-insiders/extensions
static List<VsCode> _installedWindows() {
final String progFiles86 = platform.environment['programfiles(x86)'];
final String progFiles = platform.environment['programfiles'];
final Map<String, String> stable = <String, String>{
fs.path.join(progFiles86, 'Microsoft VS Code'): '.vscode',
fs.path.join(progFiles, 'Microsoft VS Code'): '.vscode'
};
final Map<String, String> insiders = <String, String>{
fs.path.join(progFiles86, 'Microsoft VS Code Insiders'):
'.vscode-insiders',
fs.path.join(progFiles, 'Microsoft VS Code Insiders'): '.vscode-insiders'
};
return _findInstalled(stable, insiders);
}
// Linux:
// /usr/share/code/bin/code
// /usr/share/code-insiders/bin/code-insiders
// Linux Extensions:
// $HOME/.vscode/extensions
// $HOME/.vscode-insiders/extensions
static List<VsCode> _installedLinux() {
return _findInstalled(
<String, String>{'/usr/share/code': '.vscode'},
<String, String>{'/usr/share/code-insiders': '.vscode-insiders'}
);
}
static List<VsCode> _findInstalled(
Map<String, String> stable, Map<String, String> insiders) {
final Map<String, String> allPaths = <String, String>{};
allPaths.addAll(stable);
if (_includeInsiders)
allPaths.addAll(insiders);
final List<VsCode> results = <VsCode>[];
for (String directory in allPaths.keys) {
if (fs.directory(directory).existsSync()) {
final String extensionDirectory =
fs.path.join(homeDirPath, allPaths[directory], 'extensions');
results.add(new VsCode.fromDirectory(directory, extensionDirectory));
}
}
return results;
}
@override
String toString() =>
'VS Code ($version)${(_extensionVersion != Version.unknown ? ', Dart Code ($_extensionVersion)' : '')}';
static String _getVersionFromPackageJson(String packageJsonPath) {
if (!fs.isFileSync(packageJsonPath))
return null;
final String jsonString = fs.file(packageJsonPath).readAsStringSync();
final Map<String, String> json = JSON.decode(jsonString);
return json['version'];
}
}
// Copyright 2018 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:async';
import '../base/version.dart';
import '../doctor.dart';
import 'vscode.dart';
class VsCodeValidator extends DoctorValidator {
static const String extensionMarketplaceUrl =
'https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code';
final VsCode _vsCode;
VsCodeValidator(this._vsCode) : super('VS Code');
static Iterable<DoctorValidator> get installedValidators {
return VsCode
.allInstalled()
.map((VsCode vsCode) => new VsCodeValidator(vsCode));
}
@override
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType type = ValidationType.missing;
final String vsCodeVersionText = _vsCode.version == Version.unknown
? null
: 'version ${_vsCode.version}';
messages.add(new ValidationMessage('VS Code at ${_vsCode.directory}'));
if (_vsCode.isValid) {
type = ValidationType.installed;
messages.addAll(_vsCode.validationMessages
.map((String m) => new ValidationMessage(m)));
} else {
type = ValidationType.partial;
messages.addAll(_vsCode.validationMessages
.map((String m) => new ValidationMessage.error(m)));
messages.add(new ValidationMessage(
'Dart Code extension not installed; install from\n$extensionMarketplaceUrl'));
}
return new ValidationResult(type, messages, statusInfo: vsCodeVersionText);
}
}
...@@ -6,6 +6,8 @@ import 'dart:async'; ...@@ -6,6 +6,8 @@ import 'dart:async';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/vscode/vscode.dart';
import 'package:flutter_tools/src/vscode/vscode_validator.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../src/context.dart'; import '../src/context.dart';
...@@ -27,6 +29,36 @@ void main() { ...@@ -27,6 +29,36 @@ void main() {
expect(message.message, contains('Flutter plugin version 0.1.3')); expect(message.message, contains('Flutter plugin version 0.1.3'));
expect(message.message, contains('recommended minimum version')); expect(message.message, contains('recommended minimum version'));
}); });
testUsingContext('vs code validator when both installed', () async {
final ValidationResult result = await VsCodeValidatorTestTargets.installedWithExtension.validate();
expect(result.type, ValidationType.installed);
expect(result.statusInfo, 'version 1.2.3');
expect(result.messages, hasLength(2));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Dart Code '));
expect(message.message, 'Dart Code extension version 4.5.6');
});
testUsingContext('vs code validator when extension missing', () async {
final ValidationResult result = await VsCodeValidatorTestTargets.installedWithoutExtension.validate();
expect(result.type, ValidationType.partial);
expect(result.statusInfo, 'version 1.2.3');
expect(result.messages, hasLength(2));
ValidationMessage message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('VS Code '));
expect(message.message, 'VS Code at ${VsCodeValidatorTestTargets.validInstall}');
message = result.messages
.firstWhere((ValidationMessage m) => m.message.startsWith('Dart Code '));
expect(message.message, startsWith('Dart Code extension not installed'));
});
}); });
group('doctor with fake validators', () { group('doctor with fake validators', () {
...@@ -237,3 +269,17 @@ class FakeQuietDoctor extends Doctor { ...@@ -237,3 +269,17 @@ class FakeQuietDoctor extends Doctor {
return _validators; return _validators;
} }
} }
class VsCodeValidatorTestTargets extends VsCodeValidator {
static final String validInstall = fs.path.join('test', 'data', 'vscode', 'application');
static final String validExtensions = fs.path.join('test', 'data', 'vscode', 'extensions');
static final String missingExtensions = fs.path.join('test', 'data', 'vscode', 'notExtensions');
VsCodeValidatorTestTargets._(String installDirectory, String extensionDirectory)
: super(new VsCode.fromDirectory(installDirectory, extensionDirectory));
static VsCodeValidatorTestTargets get installedWithExtension =>
new VsCodeValidatorTestTargets._(validInstall, validExtensions);
static VsCodeValidatorTestTargets get installedWithoutExtension =>
new VsCodeValidatorTestTargets._(validInstall, missingExtensions);
}
{
"name": "fake-vs-code-install-for-tests",
"version": "1.2.3"
}
This file is here only to ensure the parent folder is stored in Git to act as a fake extension folder for tests.
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