Unverified Commit 466681ca authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Improve error message when a plugin sets an invalid android package (#48104)

parent 7f134d86
...@@ -85,33 +85,47 @@ class AndroidPlugin extends PluginPlatform { ...@@ -85,33 +85,47 @@ class AndroidPlugin extends PluginPlatform {
'src', 'src',
'main', 'main',
); );
File mainPluginClass = globals.fs.file(
final List<String> mainClassCandidates = <String>[
globals.fs.path.join( globals.fs.path.join(
baseMainPath, baseMainPath,
'java', 'java',
package.replaceAll('.', globals.fs.path.separator), package.replaceAll('.', globals.fs.path.separator),
'$pluginClass.java', '$pluginClass.java',
),
globals.fs.path.join(
baseMainPath,
'kotlin',
package.replaceAll('.', globals.fs.path.separator),
'$pluginClass.kt',
) )
); ];
// Check if the plugin is implemented in Kotlin since the plugin's pubspec.yaml
// doesn't include this information. File mainPluginClass;
if (!mainPluginClass.existsSync()) { bool mainClassFound = false;
mainPluginClass = globals.fs.file( for (final String mainClassCandidate in mainClassCandidates) {
globals.fs.path.join( mainPluginClass = globals.fs.file(mainClassCandidate);
baseMainPath, if (mainPluginClass.existsSync()) {
'kotlin', mainClassFound = true;
package.replaceAll('.', globals.fs.path.separator), break;
'$pluginClass.kt', }
) }
if (!mainClassFound) {
assert(mainClassCandidates.length <= 2);
throwToolExit(
'The plugin `$name` doesn\'t have a main class defined in ${mainClassCandidates.join(' or ')}. '
'This is likely to due to an incorrect `androidPackage: $package` or `mainClass` entry in the plugin\'s pubspec.yaml.\n'
'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. '
'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile. '
); );
} }
assert(mainPluginClass.existsSync());
String mainClassContent; String mainClassContent;
try { try {
mainClassContent = mainPluginClass.readAsStringSync(); mainClassContent = mainPluginClass.readAsStringSync();
} on FileSystemException { } on FileSystemException {
throwToolExit( throwToolExit(
'Couldn\'t read file $mainPluginClass even though it exists. ' 'Couldn\'t read file ${mainPluginClass.path} even though it exists. '
'Please verify that this file has read permission and try again.' 'Please verify that this file has read permission and try again.'
); );
} }
......
...@@ -111,12 +111,15 @@ void main() { ...@@ -111,12 +111,15 @@ void main() {
}); });
test('no unauthorized imports of package:path', () { test('no unauthorized imports of package:path', () {
final String whitelistedPath = globals.fs.path.join(flutterTools, 'lib', 'src', 'build_runner', 'web_compilation_delegate.dart'); final List<String> whitelistedPath = <String>[
globals.fs.path.join(flutterTools, 'lib', 'src', 'build_runner', 'web_compilation_delegate.dart'),
globals.fs.path.join(flutterTools, 'test', 'general.shard', 'platform_plugins_test.dart'),
];
for (final String dirName in <String>['lib', 'bin', 'test']) { for (final String dirName in <String>['lib', 'bin', 'test']) {
final Iterable<File> files = globals.fs.directory(globals.fs.path.join(flutterTools, dirName)) final Iterable<File> files = globals.fs.directory(globals.fs.path.join(flutterTools, dirName))
.listSync(recursive: true) .listSync(recursive: true)
.where(_isDartFile) .where(_isDartFile)
.where((FileSystemEntity entity) => entity.path != whitelistedPath) .where((FileSystemEntity entity) => !whitelistedPath.contains(entity.path))
.map(_asFile); .map(_asFile);
for (final File file in files) { for (final File file in files) {
for (final String line in file.readAsLinesSync()) { for (final String line in file.readAsLinesSync()) {
......
// 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:file/file.dart';
import 'package:flutter_tools/src/platform_plugins.dart';
import 'package:mockito/mockito.dart';
import 'package:path/path.dart' as p;
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('AndroidPlugin', () {
MockFileSystem mockFileSystem;
MockPathContext pathContext;
setUp(() {
pathContext = MockPathContext();
when(pathContext.separator).thenReturn('/');
mockFileSystem = MockFileSystem();
when(mockFileSystem.path).thenReturn(pathContext);
});
testUsingContext('throws tool exit if the plugin main class can\'t be read', () {
when(pathContext.join('.pub_cache/plugin_a', 'android', 'src', 'main'))
.thenReturn('.pub_cache/plugin_a/android/src/main');
when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'java', 'com/company', 'PluginA.java'))
.thenReturn('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java');
when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'kotlin', 'com/company', 'PluginA.kt'))
.thenReturn('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt');
final MockFile pluginJavaMainClass = MockFile();
when(pluginJavaMainClass.existsSync()).thenReturn(true);
when(pluginJavaMainClass.readAsStringSync()).thenThrow(const FileSystemException());
when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java'))
.thenReturn(pluginJavaMainClass);
final MockFile pluginKotlinMainClass = MockFile();
when(pluginKotlinMainClass.existsSync()).thenReturn(false);
when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt'))
.thenReturn(pluginKotlinMainClass);
expect(() {
AndroidPlugin(
name: 'pluginA',
package: 'com.company',
pluginClass: 'PluginA',
pluginPath: '.pub_cache/plugin_a',
).toMap();
}, throwsToolExit(
message: 'Couldn\'t read file null even though it exists. '
'Please verify that this file has read permission and try again.'
));
}, overrides: <Type, Generator>{
FileSystem: () => mockFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
}
class MockFile extends Mock implements File {}
class MockFileSystem extends Mock implements FileSystem {}
class MockPathContext extends Mock implements p.Context {}
...@@ -146,6 +146,36 @@ flutter: ...@@ -146,6 +146,36 @@ flutter:
); );
} }
void createPluginWithInvalidAndroidPackage() {
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_invalid_package.');
pluginUsingJavaAndNewEmbeddingDir
.childFile('pubspec.yaml')
.writeAsStringSync('''
flutter:
plugin:
androidPackage: plugin1.invalid
pluginClass: UseNewEmbedding
''');
pluginUsingJavaAndNewEmbeddingDir
.childDirectory('android')
.childDirectory('src')
.childDirectory('main')
.childDirectory('java')
.childDirectory('plugin1')
.childDirectory('correct')
.childFile('UseNewEmbedding.java')
..createSync(recursive: true)
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
flutterProject.directory
.childFile('.packages')
.writeAsStringSync(
'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
mode: FileMode.append,
);
}
void createNewKotlinPlugin2() { void createNewKotlinPlugin2() {
final Directory pluginUsingKotlinAndNewEmbeddingDir = final Directory pluginUsingKotlinAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_kotlin_and_new_embedding_dir.'); fs.systemTempDirectory.createTempSync('flutter_plugin_using_kotlin_and_new_embedding_dir.');
...@@ -556,6 +586,33 @@ dependencies: ...@@ -556,6 +586,33 @@ dependencies:
XcodeProjectInterpreter: () => xcodeProjectInterpreter, XcodeProjectInterpreter: () => xcodeProjectInterpreter,
}); });
// Issue: https://github.com/flutter/flutter/issues/47803
testUsingContext('exits the tool if a plugin sets an invalid android package in pubspec.yaml', () async {
when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
createPluginWithInvalidAndroidPackage();
await expectLater(
() async {
await injectPlugins(flutterProject);
},
throwsToolExit(
message: 'The plugin `plugin1` doesn\'t have a main class defined in '
'/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/java/plugin1/invalid/UseNewEmbedding.java or '
'/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/kotlin/plugin1/invalid/UseNewEmbedding.kt. '
'This is likely to due to an incorrect `androidPackage: plugin1.invalid` or `mainClass` entry in the plugin\'s pubspec.yaml.\n'
'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. '
'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile.',
),
);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => featureFlags,
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
});
testUsingContext('old embedding app uses a plugin that supports v1 and v2 embedding', () async { testUsingContext('old embedding app uses a plugin that supports v1 and v2 embedding', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1); when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
...@@ -776,6 +833,7 @@ class MockAndroidProject extends Mock implements AndroidProject {} ...@@ -776,6 +833,7 @@ class MockAndroidProject extends Mock implements AndroidProject {}
class MockFeatureFlags extends Mock implements FeatureFlags {} class MockFeatureFlags extends Mock implements FeatureFlags {}
class MockFlutterProject extends Mock implements FlutterProject {} class MockFlutterProject extends Mock implements FlutterProject {}
class MockFile extends Mock implements File {} class MockFile extends Mock implements File {}
class MockFileSystem extends Mock implements FileSystem {}
class MockIosProject extends Mock implements IosProject {} class MockIosProject extends Mock implements IosProject {}
class MockMacOSProject extends Mock implements MacOSProject {} class MockMacOSProject extends Mock implements MacOSProject {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {} class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
......
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