// 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; } } }