blob: 208370e0a6ff0308746fe39129f5a12810778bef [file] [log] [blame]
// 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:archive/archive.dart';
import '../base/file_system.dart';
import '../base/version.dart';
import '../convert.dart';
import '../doctor_validator.dart';
/// A parser for the Intellij and Android Studio plugin JAR files.
///
/// This searches on the provided plugin path for a JAR archive, then
/// unzips it to parse the META-INF/plugin.xml for version information.
///
/// In particular, the Intellij Flutter plugin has a different structure depending on the version.
///
/// For flutter-intellij plugin v49 or lower:
/// flutter-intellij/lib/flutter-intellij.jar ( includes META-INF/plugin.xml )
///
/// For flutter-intellij plugin v50 or higher and for Intellij 2020.2:
/// flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
/// flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
/// flutter-studio-X.Y.Z.jar
///
/// For flutter-intellij plugin v50 or higher and for Intellij 2020.3:
/// flutter-intellij/lib/flutter-intellij-X.Y.Z.jar
/// flutter-idea-X.Y.Z.jar ( includes META-INF/plugin.xml )
///
/// where X.Y.Z is the version number.
///
/// Intellij Flutter plugin's files can be found here:
/// https://plugins.jetbrains.com/plugin/9212-flutter/versions/stable
///
/// See also:
/// * [IntellijValidator], the validator base class that uses this to check
/// plugin versions.
class IntelliJPlugins {
IntelliJPlugins(this.pluginsPath, {
required FileSystem fileSystem
}) : _fileSystem = fileSystem;
final FileSystem _fileSystem;
final String pluginsPath;
static final Version kMinFlutterPluginVersion = Version(16, 0, 0);
static const String kIntellijDartPluginUrl = 'https://plugins.jetbrains.com/plugin/6351-dart';
static const String kIntellijFlutterPluginUrl = 'https://plugins.jetbrains.com/plugin/9212-flutter';
void validatePackage(
List<ValidationMessage> messages,
List<String> packageNames,
String title,
String url, {
Version? minVersion,
}) {
for (final String packageName in packageNames) {
if (!_hasPackage(packageName)) {
continue;
}
final String? versionText = _readPackageVersion(packageName);
final Version? version = Version.parse(versionText);
if (version != null && minVersion != null && version < minVersion) {
messages.add(ValidationMessage.error(
'$title plugin version $versionText - the recommended minimum version is $minVersion'),
);
} else {
messages.add(ValidationMessage(
'$title plugin ${version != null ? "version $version" : "installed"}'),
);
}
return;
}
messages.add(ValidationMessage(
'$title plugin can be installed from:',
contextUrl: url,
));
}
bool _hasPackage(String packageName) {
final String packagePath = _fileSystem.path.join(pluginsPath, packageName);
if (packageName.endsWith('.jar')) {
return _fileSystem.isFileSync(packagePath);
}
return _fileSystem.isDirectorySync(packagePath);
}
ArchiveFile? _findPluginXml(String packageName) {
final List<File> mainJarFileList = <File>[];
if (packageName.endsWith('.jar')) {
// package exists (checked in _hasPackage)
mainJarFileList.add(_fileSystem.file(_fileSystem.path.join(pluginsPath, packageName)));
} else {
final String packageLibPath =
_fileSystem.path.join(pluginsPath, packageName, 'lib');
if (!_fileSystem.isDirectorySync(packageLibPath)) {
return null;
}
// Collect the files with a file suffix of .jar/.zip that contains the plugin.xml file
final List<File> pluginJarFiles = _fileSystem
.directory(_fileSystem.path.join(pluginsPath, packageName, 'lib'))
.listSync()
.whereType<File>()
.where((File file) {
final String fileExt= _fileSystem.path.extension(file.path);
return fileExt == '.jar' || fileExt == '.zip';
})
.toList();
if (pluginJarFiles.isEmpty) {
return null;
}
// Prefer file with the same suffix as the package name
pluginJarFiles.sort((File a, File b) {
final bool aStartWithPackageName =
a.basename.toLowerCase().startsWith(packageName.toLowerCase());
final bool bStartWithPackageName =
b.basename.toLowerCase().startsWith(packageName.toLowerCase());
if (bStartWithPackageName != aStartWithPackageName) {
return bStartWithPackageName ? 1 : -1;
}
return a.basename.length - b.basename.length;
});
mainJarFileList.addAll(pluginJarFiles);
}
for (final File file in mainJarFileList) {
final Archive archive = ZipDecoder().decodeBytes(file.readAsBytesSync());
final ArchiveFile? archiveFile = archive.findFile('META-INF/plugin.xml');
if (archiveFile != null) {
return archiveFile;
}
}
return null;
}
String? _readPackageVersion(String packageName) {
try {
final ArchiveFile? archiveFile = _findPluginXml(packageName);
if (archiveFile == null) {
return null;
}
final String content = utf8.decode(archiveFile.content as List<int>);
const String versionStartTag = '<version>';
final int start = content.indexOf(versionStartTag);
final int end = content.indexOf('</version>', start);
return content.substring(start + versionStartTag.length, end);
} on ArchiveException {
return null;
}
}
}