mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
551 lines
19 KiB
Dart
551 lines
19 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 'package:process/process.dart';
|
|
|
|
import '../base/file_system.dart';
|
|
import '../base/io.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/user_messages.dart' hide userMessages;
|
|
import '../base/version.dart';
|
|
import '../convert.dart';
|
|
import '../doctor_validator.dart';
|
|
import '../ios/plist_parser.dart';
|
|
import 'intellij.dart';
|
|
|
|
const String _ultimateEditionTitle = 'IntelliJ IDEA Ultimate Edition';
|
|
const String _ultimateEditionId = 'IntelliJIdea';
|
|
const String _communityEditionTitle = 'IntelliJ IDEA Community Edition';
|
|
const String _communityEditionId = 'IdeaIC';
|
|
|
|
/// A doctor validator for both Intellij and Android Studio.
|
|
abstract class IntelliJValidator extends DoctorValidator {
|
|
IntelliJValidator(super.title, this.installPath, {
|
|
required FileSystem fileSystem,
|
|
required UserMessages userMessages,
|
|
}) : _fileSystem = fileSystem,
|
|
_userMessages = userMessages;
|
|
|
|
final String installPath;
|
|
final FileSystem _fileSystem;
|
|
final UserMessages _userMessages;
|
|
|
|
String get version;
|
|
|
|
String? get pluginsPath;
|
|
|
|
static const Map<String, String> _idToTitle = <String, String>{
|
|
_ultimateEditionId: _ultimateEditionTitle,
|
|
_communityEditionId: _communityEditionTitle,
|
|
};
|
|
|
|
static final Version kMinIdeaVersion = Version(2017, 1, 0);
|
|
|
|
/// Create a [DoctorValidator] for each installation of Intellij.
|
|
///
|
|
/// On platforms other than macOS, Linux, and Windows this returns an
|
|
/// empty list.
|
|
static Iterable<DoctorValidator> installedValidators({
|
|
required FileSystem fileSystem,
|
|
required Platform platform,
|
|
required UserMessages userMessages,
|
|
required PlistParser plistParser,
|
|
required ProcessManager processManager,
|
|
}) {
|
|
final FileSystemUtils fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform);
|
|
if (platform.isWindows) {
|
|
return IntelliJValidatorOnWindows.installed(
|
|
fileSystem: fileSystem,
|
|
fileSystemUtils: fileSystemUtils,
|
|
platform: platform,
|
|
userMessages: userMessages,
|
|
);
|
|
}
|
|
if (platform.isLinux) {
|
|
return IntelliJValidatorOnLinux.installed(
|
|
fileSystem: fileSystem,
|
|
fileSystemUtils: fileSystemUtils,
|
|
userMessages: userMessages,
|
|
);
|
|
}
|
|
if (platform.isMacOS) {
|
|
return IntelliJValidatorOnMac.installed(
|
|
fileSystem: fileSystem,
|
|
fileSystemUtils: fileSystemUtils,
|
|
userMessages: userMessages,
|
|
plistParser: plistParser,
|
|
processManager: processManager,
|
|
);
|
|
}
|
|
return <DoctorValidator>[];
|
|
}
|
|
|
|
@override
|
|
Future<ValidationResult> validate() async {
|
|
final List<ValidationMessage> messages = <ValidationMessage>[];
|
|
|
|
if (pluginsPath == null) {
|
|
messages.add(const ValidationMessage.error('Invalid IntelliJ version number.'));
|
|
} else {
|
|
messages.add(ValidationMessage(_userMessages.intellijLocation(installPath)));
|
|
|
|
final IntelliJPlugins plugins = IntelliJPlugins(pluginsPath!, fileSystem: _fileSystem);
|
|
plugins.validatePackage(
|
|
messages,
|
|
<String>['flutter-intellij', 'flutter-intellij.jar'],
|
|
'Flutter',
|
|
IntelliJPlugins.kIntellijFlutterPluginUrl,
|
|
minVersion: IntelliJPlugins.kMinFlutterPluginVersion,
|
|
);
|
|
plugins.validatePackage(
|
|
messages,
|
|
<String>['Dart'],
|
|
'Dart',
|
|
IntelliJPlugins.kIntellijDartPluginUrl,
|
|
);
|
|
|
|
if (_hasIssues(messages)) {
|
|
messages.add(ValidationMessage(_userMessages.intellijPluginInfo));
|
|
}
|
|
|
|
_validateIntelliJVersion(messages, kMinIdeaVersion);
|
|
}
|
|
|
|
return ValidationResult(
|
|
_hasIssues(messages) ? ValidationType.partial : ValidationType.success,
|
|
messages,
|
|
statusInfo: _userMessages.intellijStatusInfo(version),
|
|
);
|
|
}
|
|
|
|
bool _hasIssues(List<ValidationMessage> messages) {
|
|
return messages.any((ValidationMessage message) => message.isError);
|
|
}
|
|
|
|
void _validateIntelliJVersion(List<ValidationMessage> messages, Version minVersion) {
|
|
final Version? installedVersion = Version.parse(version);
|
|
if (installedVersion == null) {
|
|
return;
|
|
}
|
|
|
|
if (installedVersion < minVersion) {
|
|
messages.add(ValidationMessage.error(_userMessages.intellijMinimumVersion(minVersion.toString())));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A windows specific implementation of the intellij validator.
|
|
class IntelliJValidatorOnWindows extends IntelliJValidator {
|
|
IntelliJValidatorOnWindows(String title, this.version, String installPath, this.pluginsPath, {
|
|
required FileSystem fileSystem,
|
|
required UserMessages userMessages,
|
|
}) : super(title, installPath, fileSystem: fileSystem, userMessages: userMessages);
|
|
|
|
@override
|
|
final String version;
|
|
|
|
@override
|
|
final String pluginsPath;
|
|
|
|
static Iterable<DoctorValidator> installed({
|
|
required FileSystem fileSystem,
|
|
required FileSystemUtils fileSystemUtils,
|
|
required Platform platform,
|
|
required UserMessages userMessages,
|
|
}) {
|
|
final List<DoctorValidator> validators = <DoctorValidator>[];
|
|
if (fileSystemUtils.homeDirPath == null) {
|
|
return validators;
|
|
}
|
|
|
|
void addValidator(String title, String version, String installPath, String pluginsPath) {
|
|
final IntelliJValidatorOnWindows validator = IntelliJValidatorOnWindows(
|
|
title,
|
|
version,
|
|
installPath,
|
|
pluginsPath,
|
|
fileSystem: fileSystem,
|
|
userMessages: userMessages,
|
|
);
|
|
for (int index = 0; index < validators.length; index += 1) {
|
|
final DoctorValidator other = validators[index];
|
|
if (other is IntelliJValidatorOnWindows && validator.installPath == other.installPath) {
|
|
if (validator.version.compareTo(other.version) > 0) {
|
|
validators[index] = validator;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
validators.add(validator);
|
|
}
|
|
|
|
// before IntelliJ 2019
|
|
final Directory homeDir = fileSystem.directory(fileSystemUtils.homeDirPath);
|
|
for (final Directory dir in homeDir.listSync().whereType<Directory>()) {
|
|
final String name = fileSystem.path.basename(dir.path);
|
|
IntelliJValidator._idToTitle.forEach((String id, String title) {
|
|
if (name.startsWith('.$id')) {
|
|
final String version = name.substring(id.length + 1);
|
|
String? installPath;
|
|
try {
|
|
installPath = fileSystem.file(fileSystem.path.join(dir.path, 'system', '.home')).readAsStringSync();
|
|
} on FileSystemException {
|
|
// ignored
|
|
}
|
|
if (installPath != null && fileSystem.isDirectorySync(installPath)) {
|
|
final String pluginsPath = fileSystem.path.join(dir.path, 'config', 'plugins');
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// after IntelliJ 2020
|
|
if (!platform.environment.containsKey('LOCALAPPDATA')) {
|
|
return validators;
|
|
}
|
|
final Directory cacheDir = fileSystem.directory(fileSystem.path.join(platform.environment['LOCALAPPDATA']!, 'JetBrains'));
|
|
if (!cacheDir.existsSync()) {
|
|
return validators;
|
|
}
|
|
for (final Directory dir in cacheDir.listSync().whereType<Directory>()) {
|
|
final String name = fileSystem.path.basename(dir.path);
|
|
IntelliJValidator._idToTitle.forEach((String id, String title) {
|
|
if (name.startsWith(id)) {
|
|
final String version = name.substring(id.length);
|
|
String? installPath;
|
|
try {
|
|
installPath = fileSystem.file(fileSystem.path.join(dir.path, '.home')).readAsStringSync();
|
|
} on FileSystemException {
|
|
// ignored
|
|
}
|
|
if (installPath != null && fileSystem.isDirectorySync(installPath)) {
|
|
String pluginsPath;
|
|
if (fileSystem.isDirectorySync('$installPath.plugins')) {
|
|
// IntelliJ 2020.3
|
|
pluginsPath = '$installPath.plugins';
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
} else if (platform.environment.containsKey('APPDATA')) {
|
|
final String pluginsPathInAppData = fileSystem.path.join(
|
|
platform.environment['APPDATA']!, 'JetBrains', name, 'plugins');
|
|
if (fileSystem.isDirectorySync(pluginsPathInAppData)) {
|
|
// IntelliJ 2020.1 ~ 2020.2
|
|
pluginsPath = pluginsPathInAppData;
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return validators;
|
|
}
|
|
}
|
|
|
|
/// A linux specific implementation of the intellij validator.
|
|
class IntelliJValidatorOnLinux extends IntelliJValidator {
|
|
IntelliJValidatorOnLinux(String title, this.version, String installPath, this.pluginsPath, {
|
|
required FileSystem fileSystem,
|
|
required UserMessages userMessages,
|
|
}) : super(title, installPath, fileSystem: fileSystem, userMessages: userMessages);
|
|
|
|
@override
|
|
final String version;
|
|
|
|
@override
|
|
final String pluginsPath;
|
|
|
|
static Iterable<DoctorValidator> installed({
|
|
required FileSystem fileSystem,
|
|
required FileSystemUtils fileSystemUtils,
|
|
required UserMessages userMessages,
|
|
}) {
|
|
final List<DoctorValidator> validators = <DoctorValidator>[];
|
|
final String? homeDirPath = fileSystemUtils.homeDirPath;
|
|
if (homeDirPath == null) {
|
|
return validators;
|
|
}
|
|
|
|
void addValidator(String title, String version, String installPath, String pluginsPath) {
|
|
final IntelliJValidatorOnLinux validator = IntelliJValidatorOnLinux(
|
|
title,
|
|
version,
|
|
installPath,
|
|
pluginsPath,
|
|
fileSystem: fileSystem,
|
|
userMessages: userMessages,
|
|
);
|
|
for (int index = 0; index < validators.length; index += 1) {
|
|
final DoctorValidator other = validators[index];
|
|
if (other is IntelliJValidatorOnLinux && validator.installPath == other.installPath) {
|
|
if (validator.version.compareTo(other.version) > 0) {
|
|
validators[index] = validator;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
validators.add(validator);
|
|
}
|
|
|
|
// before IntelliJ 2019
|
|
final Directory homeDir = fileSystem.directory(homeDirPath);
|
|
for (final Directory dir in homeDir.listSync().whereType<Directory>()) {
|
|
final String name = fileSystem.path.basename(dir.path);
|
|
IntelliJValidator._idToTitle.forEach((String id, String title) {
|
|
if (name.startsWith('.$id')) {
|
|
final String version = name.substring(id.length + 1);
|
|
String? installPath;
|
|
try {
|
|
installPath = fileSystem.file(fileSystem.path.join(dir.path, 'system', '.home')).readAsStringSync();
|
|
} on FileSystemException {
|
|
// ignored
|
|
}
|
|
if (installPath != null && fileSystem.isDirectorySync(installPath)) {
|
|
final String pluginsPath = fileSystem.path.join(dir.path, 'config', 'plugins');
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
// after IntelliJ 2020 ~
|
|
final Directory cacheDir = fileSystem.directory(fileSystem.path.join(homeDirPath, '.cache', 'JetBrains'));
|
|
if (!cacheDir.existsSync()) {
|
|
return validators;
|
|
}
|
|
for (final Directory dir in cacheDir.listSync().whereType<Directory>()) {
|
|
final String name = fileSystem.path.basename(dir.path);
|
|
IntelliJValidator._idToTitle.forEach((String id, String title) {
|
|
if (name.startsWith(id)) {
|
|
final String version = name.substring(id.length);
|
|
String? installPath;
|
|
try {
|
|
installPath = fileSystem.file(fileSystem.path.join(dir.path, '.home')).readAsStringSync();
|
|
} on FileSystemException {
|
|
// ignored
|
|
}
|
|
if (installPath != null && fileSystem.isDirectorySync(installPath)) {
|
|
final String pluginsPathInUserHomeDir = fileSystem.path.join(
|
|
homeDirPath,
|
|
'.local',
|
|
'share',
|
|
'JetBrains',
|
|
name);
|
|
if (installPath.contains(fileSystem.path.join('JetBrains','Toolbox','apps'))) {
|
|
// via JetBrains ToolBox app
|
|
final String pluginsPathInInstallDir = '$installPath.plugins';
|
|
if (fileSystem.isDirectorySync(pluginsPathInUserHomeDir)) {
|
|
// after 2020.2.x
|
|
final String pluginsPath = pluginsPathInUserHomeDir;
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
} else if (fileSystem.isDirectorySync(pluginsPathInInstallDir)) {
|
|
// only 2020.1.X
|
|
final String pluginsPath = pluginsPathInInstallDir;
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
}
|
|
} else {
|
|
// via tar.gz
|
|
final String pluginsPath = pluginsPathInUserHomeDir;
|
|
addValidator(title, version, installPath, pluginsPath);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return validators;
|
|
}
|
|
}
|
|
|
|
/// A macOS specific implementation of the intellij validator.
|
|
class IntelliJValidatorOnMac extends IntelliJValidator {
|
|
IntelliJValidatorOnMac(String title, this.id, String installPath, {
|
|
required FileSystem fileSystem,
|
|
required UserMessages userMessages,
|
|
required PlistParser plistParser,
|
|
required String? homeDirPath,
|
|
}) : _plistParser = plistParser,
|
|
_homeDirPath = homeDirPath,
|
|
super(title, installPath, fileSystem: fileSystem, userMessages: userMessages);
|
|
|
|
final String id;
|
|
final PlistParser _plistParser;
|
|
final String? _homeDirPath;
|
|
|
|
static const Map<String, String> _dirNameToId = <String, String>{
|
|
'IntelliJ IDEA.app': _ultimateEditionId,
|
|
'IntelliJ IDEA Ultimate.app': _ultimateEditionId,
|
|
'IntelliJ IDEA CE.app': _communityEditionId,
|
|
};
|
|
|
|
static Iterable<DoctorValidator> installed({
|
|
required FileSystem fileSystem,
|
|
required FileSystemUtils fileSystemUtils,
|
|
required UserMessages userMessages,
|
|
required PlistParser plistParser,
|
|
required ProcessManager processManager,
|
|
}) {
|
|
final List<DoctorValidator> validators = <DoctorValidator>[];
|
|
final String? homeDirPath = fileSystemUtils.homeDirPath;
|
|
final List<String> installPaths = <String>[
|
|
'/Applications',
|
|
if (homeDirPath != null)
|
|
fileSystem.path.join(homeDirPath, 'Applications'),
|
|
];
|
|
|
|
void checkForIntelliJ(Directory dir) {
|
|
final String name = fileSystem.path.basename(dir.path);
|
|
_dirNameToId.forEach((String dirName, String id) {
|
|
if (name == dirName) {
|
|
assert(IntelliJValidator._idToTitle.containsKey(id));
|
|
final String title = IntelliJValidator._idToTitle[id]!;
|
|
validators.add(IntelliJValidatorOnMac(
|
|
title,
|
|
id,
|
|
dir.path,
|
|
fileSystem: fileSystem,
|
|
userMessages: userMessages,
|
|
plistParser: plistParser,
|
|
homeDirPath: homeDirPath,
|
|
));
|
|
}
|
|
});
|
|
}
|
|
|
|
try {
|
|
final Iterable<Directory> installDirs = installPaths
|
|
.map(fileSystem.directory)
|
|
.map<List<FileSystemEntity>>((Directory dir) => dir.existsSync() ? dir.listSync() : <FileSystemEntity>[])
|
|
.expand<FileSystemEntity>((List<FileSystemEntity> mappedDirs) => mappedDirs)
|
|
.whereType<Directory>();
|
|
for (final Directory dir in installDirs) {
|
|
checkForIntelliJ(dir);
|
|
if (!dir.path.endsWith('.app')) {
|
|
for (final FileSystemEntity subdirectory in dir.listSync()) {
|
|
if (subdirectory is Directory) {
|
|
checkForIntelliJ(subdirectory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Query Spotlight for unexpected installation locations.
|
|
String ceSpotlightResult = '';
|
|
String ultimateSpotlightResult = '';
|
|
try {
|
|
final ProcessResult ceQueryResult = processManager.runSync(<String>[
|
|
'mdfind',
|
|
'kMDItemCFBundleIdentifier="com.jetbrains.intellij.ce"',
|
|
]);
|
|
ceSpotlightResult = ceQueryResult.stdout as String;
|
|
final ProcessResult ultimateQueryResult = processManager.runSync(<String>[
|
|
'mdfind',
|
|
'kMDItemCFBundleIdentifier="com.jetbrains.intellij*"',
|
|
]);
|
|
ultimateSpotlightResult = ultimateQueryResult.stdout as String;
|
|
} on ProcessException {
|
|
// The Spotlight query is a nice-to-have, continue checking known installation locations.
|
|
}
|
|
|
|
for (final String installPath in LineSplitter.split(ceSpotlightResult)) {
|
|
if (!validators.whereType<IntelliJValidatorOnMac>().any((IntelliJValidatorOnMac e) => e.installPath == installPath)) {
|
|
validators.add(IntelliJValidatorOnMac(
|
|
_communityEditionTitle,
|
|
_communityEditionId,
|
|
installPath,
|
|
fileSystem: fileSystem,
|
|
userMessages: userMessages,
|
|
plistParser: plistParser,
|
|
homeDirPath: homeDirPath,
|
|
));
|
|
}
|
|
}
|
|
|
|
for (final String installPath in LineSplitter.split(ultimateSpotlightResult)) {
|
|
if (!validators.whereType<IntelliJValidatorOnMac>().any((IntelliJValidatorOnMac e) => e.installPath == installPath)) {
|
|
validators.add(IntelliJValidatorOnMac(
|
|
_ultimateEditionTitle,
|
|
_ultimateEditionId,
|
|
installPath,
|
|
fileSystem: fileSystem,
|
|
userMessages: userMessages,
|
|
plistParser: plistParser,
|
|
homeDirPath: homeDirPath,
|
|
));
|
|
}
|
|
}
|
|
} on FileSystemException catch (e) {
|
|
validators.add(ValidatorWithResult(
|
|
userMessages.intellijMacUnknownResult,
|
|
ValidationResult(ValidationType.missing, <ValidationMessage>[
|
|
ValidationMessage.error(e.message),
|
|
]),
|
|
));
|
|
}
|
|
return validators;
|
|
}
|
|
|
|
@visibleForTesting
|
|
String get plistFile {
|
|
_plistFile ??= _fileSystem.path.join(installPath, 'Contents', 'Info.plist');
|
|
return _plistFile!;
|
|
}
|
|
String? _plistFile;
|
|
|
|
@override
|
|
String get version {
|
|
return _version ??= _plistParser.getValueFromFile<String>(
|
|
plistFile,
|
|
PlistParser.kCFBundleShortVersionStringKey,
|
|
) ?? 'unknown';
|
|
}
|
|
String? _version;
|
|
|
|
@override
|
|
String? get pluginsPath {
|
|
if (_pluginsPath != null) {
|
|
return _pluginsPath!;
|
|
}
|
|
|
|
final String? altLocation = _plistParser
|
|
.getValueFromFile<String>(plistFile, 'JetBrainsToolboxApp');
|
|
|
|
if (altLocation != null) {
|
|
_pluginsPath = '$altLocation.plugins';
|
|
return _pluginsPath!;
|
|
}
|
|
|
|
final List<String> split = version.split('.');
|
|
if (split.length < 2) {
|
|
return null;
|
|
}
|
|
final String major = split[0];
|
|
final String minor = split[1];
|
|
|
|
final String? homeDirPath = _homeDirPath;
|
|
if (homeDirPath != null) {
|
|
String pluginsPath = _fileSystem.path.join(
|
|
homeDirPath,
|
|
'Library',
|
|
'Application Support',
|
|
'JetBrains',
|
|
'$id$major.$minor',
|
|
'plugins',
|
|
);
|
|
// Fallback to legacy location from < 2020.
|
|
if (!_fileSystem.isDirectorySync(pluginsPath)) {
|
|
pluginsPath = _fileSystem.path.join(
|
|
homeDirPath,
|
|
'Library',
|
|
'Application Support',
|
|
'$id$major.$minor',
|
|
);
|
|
}
|
|
_pluginsPath = pluginsPath;
|
|
}
|
|
|
|
return _pluginsPath;
|
|
}
|
|
String? _pluginsPath;
|
|
}
|