mirror of
https://github.com/flutter/flutter
synced 2024-09-13 05:11:45 +00:00
Implement dartPluginClass support for plugins (#74469)
This commit is contained in:
parent
d9fca66af2
commit
b7d4806243
10
dev/devicelab/bin/tasks/dart_plugin_registry_test.dart
Normal file
10
dev/devicelab/bin/tasks/dart_plugin_registry_test.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
// 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:flutter_devicelab/tasks/dart_plugin_registry_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await task(dartPluginRegistryTest());
|
||||
}
|
181
dev/devicelab/lib/tasks/dart_plugin_registry_tests.dart
Normal file
181
dev/devicelab/lib/tasks/dart_plugin_registry_tests.dart
Normal file
|
@ -0,0 +1,181 @@
|
|||
// 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 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/task_result.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
|
||||
TaskFunction dartPluginRegistryTest({
|
||||
String deviceIdOverride,
|
||||
Map<String, String> environment,
|
||||
}) {
|
||||
final Directory tempDir = Directory.systemTemp
|
||||
.createTempSync('flutter_devicelab_dart_plugin_test.');
|
||||
return () async {
|
||||
try {
|
||||
section('Create implementation plugin');
|
||||
await inDirectory(tempDir, () async {
|
||||
await flutter(
|
||||
'create',
|
||||
options: <String>[
|
||||
'--template=plugin',
|
||||
'--org',
|
||||
'io.flutter.devicelab',
|
||||
'--platforms',
|
||||
'macos',
|
||||
'plugin_platform_implementation',
|
||||
],
|
||||
environment: environment,
|
||||
);
|
||||
});
|
||||
|
||||
final File pluginMain = File(path.join(
|
||||
tempDir.absolute.path,
|
||||
'plugin_platform_implementation',
|
||||
'lib',
|
||||
'plugin_platform_implementation.dart',
|
||||
));
|
||||
if (!pluginMain.existsSync()) {
|
||||
return TaskResult.failure('${pluginMain.path} does not exist');
|
||||
}
|
||||
|
||||
// Patch plugin main dart file.
|
||||
await pluginMain.writeAsString('''
|
||||
class PluginPlatformInterfaceMacOS {
|
||||
static void registerWith() {
|
||||
print('PluginPlatformInterfaceMacOS.registerWith() was called');
|
||||
}
|
||||
}
|
||||
''', flush: true);
|
||||
|
||||
// Patch plugin main pubspec file.
|
||||
final File pluginImplPubspec = File(path.join(
|
||||
tempDir.absolute.path,
|
||||
'plugin_platform_implementation',
|
||||
'pubspec.yaml',
|
||||
));
|
||||
String pluginImplPubspecContent = await pluginImplPubspec.readAsString();
|
||||
pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst(
|
||||
' pluginClass: PluginPlatformImplementationPlugin',
|
||||
' pluginClass: PluginPlatformImplementationPlugin\n'
|
||||
' dartPluginClass: PluginPlatformInterfaceMacOS\n',
|
||||
);
|
||||
pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst(
|
||||
' platforms:\n',
|
||||
' implements: plugin_platform_interface\n'
|
||||
' platforms:\n');
|
||||
await pluginImplPubspec.writeAsString(pluginImplPubspecContent,
|
||||
flush: true);
|
||||
|
||||
section('Create interface plugin');
|
||||
await inDirectory(tempDir, () async {
|
||||
await flutter(
|
||||
'create',
|
||||
options: <String>[
|
||||
'--template=plugin',
|
||||
'--org',
|
||||
'io.flutter.devicelab',
|
||||
'--platforms',
|
||||
'macos',
|
||||
'plugin_platform_interface',
|
||||
],
|
||||
environment: environment,
|
||||
);
|
||||
});
|
||||
final File pluginInterfacePubspec = File(path.join(
|
||||
tempDir.absolute.path,
|
||||
'plugin_platform_interface',
|
||||
'pubspec.yaml',
|
||||
));
|
||||
String pluginInterfacePubspecContent =
|
||||
await pluginInterfacePubspec.readAsString();
|
||||
pluginInterfacePubspecContent =
|
||||
pluginInterfacePubspecContent.replaceFirst(
|
||||
' pluginClass: PluginPlatformInterfacePlugin',
|
||||
' default_package: plugin_platform_implementation\n');
|
||||
pluginInterfacePubspecContent =
|
||||
pluginInterfacePubspecContent.replaceFirst(
|
||||
'dependencies:',
|
||||
'dependencies:\n'
|
||||
' plugin_platform_implementation:\n'
|
||||
' path: ../plugin_platform_implementation\n');
|
||||
await pluginInterfacePubspec.writeAsString(pluginInterfacePubspecContent,
|
||||
flush: true);
|
||||
|
||||
section('Create app');
|
||||
|
||||
await inDirectory(tempDir, () async {
|
||||
await flutter(
|
||||
'create',
|
||||
options: <String>[
|
||||
'--template=app',
|
||||
'--org',
|
||||
'io.flutter.devicelab',
|
||||
'--platforms',
|
||||
'macos',
|
||||
'app',
|
||||
],
|
||||
environment: environment,
|
||||
);
|
||||
});
|
||||
|
||||
final File appPubspec = File(path.join(
|
||||
tempDir.absolute.path,
|
||||
'app',
|
||||
'pubspec.yaml',
|
||||
));
|
||||
String appPubspecContent = await appPubspec.readAsString();
|
||||
appPubspecContent = appPubspecContent.replaceFirst(
|
||||
'dependencies:',
|
||||
'dependencies:\n'
|
||||
' plugin_platform_interface:\n'
|
||||
' path: ../plugin_platform_interface\n');
|
||||
await appPubspec.writeAsString(appPubspecContent, flush: true);
|
||||
|
||||
section('Flutter run for macos');
|
||||
|
||||
await inDirectory(path.join(tempDir.path, 'app'), () async {
|
||||
final Process run = await startProcess(
|
||||
path.join(flutterDirectory.path, 'bin', 'flutter'),
|
||||
flutterCommandArgs('run', <String>['-d', 'macos', '-v']),
|
||||
environment: null,
|
||||
);
|
||||
Completer<void> registryExecutedCompleter = Completer<void>();
|
||||
final StreamSubscription<void> subscription = run.stdout
|
||||
.transform<String>(utf8.decoder)
|
||||
.transform<String>(const LineSplitter())
|
||||
.listen((String line) {
|
||||
if (line.contains(
|
||||
'PluginPlatformInterfaceMacOS.registerWith() was called')) {
|
||||
registryExecutedCompleter.complete();
|
||||
}
|
||||
print('stdout: $line');
|
||||
});
|
||||
|
||||
section('Wait for registry execution');
|
||||
await registryExecutedCompleter.future
|
||||
.timeout(const Duration(minutes: 1));
|
||||
|
||||
// Hot restart.
|
||||
run.stdin.write('R');
|
||||
registryExecutedCompleter = Completer<void>();
|
||||
|
||||
section('Wait for registry execution after hot restart');
|
||||
await registryExecutedCompleter.future
|
||||
.timeout(const Duration(minutes: 1));
|
||||
|
||||
subscription.cancel();
|
||||
run.kill();
|
||||
});
|
||||
return TaskResult.success(null);
|
||||
} finally {
|
||||
rmTree(tempDir);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -308,6 +308,7 @@ class Environment {
|
|||
@required Artifacts artifacts,
|
||||
@required ProcessManager processManager,
|
||||
@required String engineVersion,
|
||||
@required bool generateDartPluginRegistry,
|
||||
Directory buildDir,
|
||||
Map<String, String> defines = const <String, String>{},
|
||||
Map<String, String> inputs = const <String, String>{},
|
||||
|
@ -347,6 +348,7 @@ class Environment {
|
|||
processManager: processManager,
|
||||
engineVersion: engineVersion,
|
||||
inputs: inputs,
|
||||
generateDartPluginRegistry: generateDartPluginRegistry,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -363,6 +365,7 @@ class Environment {
|
|||
Map<String, String> defines = const <String, String>{},
|
||||
Map<String, String> inputs = const <String, String>{},
|
||||
String engineVersion,
|
||||
bool generateDartPluginRegistry = false,
|
||||
@required FileSystem fileSystem,
|
||||
@required Logger logger,
|
||||
@required Artifacts artifacts,
|
||||
|
@ -381,6 +384,7 @@ class Environment {
|
|||
artifacts: artifacts,
|
||||
processManager: processManager,
|
||||
engineVersion: engineVersion,
|
||||
generateDartPluginRegistry: generateDartPluginRegistry,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -398,6 +402,7 @@ class Environment {
|
|||
@required this.artifacts,
|
||||
@required this.engineVersion,
|
||||
@required this.inputs,
|
||||
@required this.generateDartPluginRegistry,
|
||||
});
|
||||
|
||||
/// The [Source] value which is substituted with the path to [projectDir].
|
||||
|
@ -475,6 +480,11 @@ class Environment {
|
|||
|
||||
/// The version of the current engine, or `null` if built with a local engine.
|
||||
final String engineVersion;
|
||||
|
||||
/// Whether to generate the Dart plugin registry.
|
||||
/// When [true], the main entrypoint is wrapped and the wrapper becomes
|
||||
/// the new entrypoint.
|
||||
final bool generateDartPluginRegistry;
|
||||
}
|
||||
|
||||
/// The result information from the build system.
|
||||
|
|
|
@ -279,6 +279,8 @@ class KernelSnapshot extends Target {
|
|||
fileSystemScheme: fileSystemScheme,
|
||||
dartDefines: decodeDartDefines(environment.defines, kDartDefines),
|
||||
packageConfig: packageConfig,
|
||||
buildDir: environment.buildDir,
|
||||
generateDartPluginRegistry: environment.generateDartPluginRegistry,
|
||||
);
|
||||
if (output == null || output.errorCount != 0) {
|
||||
throw Exception();
|
||||
|
|
|
@ -160,6 +160,7 @@ Future<void> buildWithAssemble({
|
|||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
processManager: globals.processManager,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
final Target target = buildMode == BuildMode.debug
|
||||
? const CopyFlutterBundle()
|
||||
|
|
|
@ -195,7 +195,8 @@ class AssembleCommand extends FlutterCommand {
|
|||
processManager: globals.processManager,
|
||||
engineVersion: globals.artifacts.isLocalEngine
|
||||
? null
|
||||
: globals.flutterVersion.engineRevision
|
||||
: globals.flutterVersion.engineRevision,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -385,6 +385,7 @@ end
|
|||
engineVersion: globals.artifacts.isLocalEngine
|
||||
? null
|
||||
: globals.flutterVersion.engineRevision,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
Target target;
|
||||
// Always build debug for simulator.
|
||||
|
|
|
@ -119,6 +119,7 @@ class PackagesGetCommand extends FlutterCommand {
|
|||
outputDir: globals.fs.directory(getBuildDirectory()),
|
||||
processManager: globals.processManager,
|
||||
projectDir: flutterProject.directory,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
|
||||
await generateLocalizationsSyntheticPackage(
|
||||
|
@ -324,6 +325,7 @@ class PackagesInteractiveGetCommand extends FlutterCommand {
|
|||
outputDir: globals.fs.directory(getBuildDirectory()),
|
||||
processManager: globals.processManager,
|
||||
projectDir: flutterProject.directory,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
|
||||
await generateLocalizationsSyntheticPackage(
|
||||
|
|
|
@ -19,6 +19,8 @@ import 'base/logger.dart';
|
|||
import 'base/platform.dart';
|
||||
import 'build_info.dart';
|
||||
import 'convert.dart';
|
||||
import 'plugins.dart';
|
||||
import 'project.dart';
|
||||
|
||||
/// The target model describes the set of core libraries that are available within
|
||||
/// the SDK.
|
||||
|
@ -209,6 +211,8 @@ class KernelCompiler {
|
|||
String fileSystemScheme,
|
||||
String initializeFromDill,
|
||||
String platformDill,
|
||||
Directory buildDir,
|
||||
bool generateDartPluginRegistry = false,
|
||||
@required String packagesPath,
|
||||
@required BuildMode buildMode,
|
||||
@required bool trackWidgetCreation,
|
||||
|
@ -227,7 +231,8 @@ class KernelCompiler {
|
|||
throwToolExit('Unable to find Dart binary at $engineDartPath');
|
||||
}
|
||||
String mainUri;
|
||||
final Uri mainFileUri = _fileSystem.file(mainPath).uri;
|
||||
final File mainFile = _fileSystem.file(mainPath);
|
||||
final Uri mainFileUri = mainFile.uri;
|
||||
if (packagesPath != null) {
|
||||
mainUri = packageConfig.toPackageUri(mainFileUri)?.toString();
|
||||
}
|
||||
|
@ -235,6 +240,21 @@ class KernelCompiler {
|
|||
if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) {
|
||||
_fileSystem.file(outputFilePath).createSync(recursive: true);
|
||||
}
|
||||
if (buildDir != null && generateDartPluginRegistry) {
|
||||
// `generated_main.dart` is under `.dart_tools/flutter_build/`,
|
||||
// so the resident compiler can find it.
|
||||
final File newMainDart = buildDir.parent.childFile('generated_main.dart');
|
||||
if (await generateMainDartWithPluginRegistrant(
|
||||
FlutterProject.current(),
|
||||
packageConfig,
|
||||
mainUri,
|
||||
newMainDart,
|
||||
mainFile,
|
||||
)) {
|
||||
mainUri = newMainDart.path;
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> command = <String>[
|
||||
engineDartPath,
|
||||
'--disable-dart-dev',
|
||||
|
@ -579,7 +599,6 @@ class DefaultResidentCompiler implements ResidentCompiler {
|
|||
if (!_controller.hasListener) {
|
||||
_controller.stream.listen(_handleCompilationRequest);
|
||||
}
|
||||
|
||||
final Completer<CompilerOutput> completer = Completer<CompilerOutput>();
|
||||
_controller.add(
|
||||
_RecompileRequest(completer, mainUri, invalidatedFiles, outputPath, packageConfig, suppressErrors)
|
||||
|
|
|
@ -518,6 +518,20 @@ class DevFS {
|
|||
// dill files that depend on the invalidated files.
|
||||
_logger.printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
|
||||
|
||||
// `generated_main.dart` contains the Dart plugin registry.
|
||||
if (projectRootPath != null) {
|
||||
final File generatedMainDart = _fileSystem.file(
|
||||
_fileSystem.path.join(
|
||||
projectRootPath,
|
||||
'.dart_tool',
|
||||
'flutter_build',
|
||||
'generated_main.dart',
|
||||
),
|
||||
);
|
||||
if (generatedMainDart != null && generatedMainDart.existsSync()) {
|
||||
mainUri = generatedMainDart.uri;
|
||||
}
|
||||
}
|
||||
// Await the compiler response after checking if the bundle is updated. This allows the file
|
||||
// stating to be done while waiting for the frontend_server response.
|
||||
final Future<CompilerOutput> pendingCompilerOutput = generator.recompile(
|
||||
|
|
|
@ -86,6 +86,13 @@ class FlutterManifest {
|
|||
/// The string value of the top-level `name` property in the `pubspec.yaml` file.
|
||||
String get appName => _descriptor['name'] as String ?? '';
|
||||
|
||||
/// Contains the name of the dependencies.
|
||||
/// These are the keys specified in the `dependency` map.
|
||||
Set<String> get dependencies {
|
||||
final YamlMap dependencies = _descriptor['dependencies'] as YamlMap;
|
||||
return dependencies != null ? <String>{...dependencies.keys.cast<String>()} : <String>{};
|
||||
}
|
||||
|
||||
// Flag to avoid printing multiple invalid version messages.
|
||||
bool _hasShowInvalidVersionMsg = false;
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ const String kPluginClass = 'pluginClass';
|
|||
/// Constant for 'pluginClass' key in plugin maps.
|
||||
const String kDartPluginClass = 'dartPluginClass';
|
||||
|
||||
// Constant for 'defaultPackage' key in plugin maps.
|
||||
const String kDefaultPackage = 'default_package';
|
||||
|
||||
/// Marker interface for all platform specific plugin config implementations.
|
||||
abstract class PluginPlatform {
|
||||
const PluginPlatform();
|
||||
|
@ -207,6 +210,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
@required this.name,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
this.defaultPackage,
|
||||
});
|
||||
|
||||
factory MacOSPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
|
@ -220,6 +224,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
name: name,
|
||||
pluginClass: pluginClass,
|
||||
dartPluginClass: yaml[kDartPluginClass] as String,
|
||||
defaultPackage: yaml[kDefaultPackage] as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -227,7 +232,9 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
if (yaml == null) {
|
||||
return false;
|
||||
}
|
||||
return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'macos';
|
||||
|
@ -235,6 +242,7 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
final String name;
|
||||
final String pluginClass;
|
||||
final String dartPluginClass;
|
||||
final String defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
|
@ -244,7 +252,8 @@ class MacOSPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
return <String, dynamic>{
|
||||
'name': name,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass : dartPluginClass,
|
||||
if (defaultPackage != null) kDefaultPackage : defaultPackage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +267,8 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
|
|||
@required this.name,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
}) : assert(pluginClass != null || dartPluginClass != null);
|
||||
this.defaultPackage,
|
||||
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
|
||||
|
||||
factory WindowsPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml));
|
||||
|
@ -271,6 +281,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
|
|||
name: name,
|
||||
pluginClass: pluginClass,
|
||||
dartPluginClass: yaml[kDartPluginClass] as String,
|
||||
defaultPackage: yaml[kDefaultPackage] as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -278,7 +289,9 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
|
|||
if (yaml == null) {
|
||||
return false;
|
||||
}
|
||||
return yaml[kDartPluginClass] is String || yaml[kPluginClass] is String;
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'windows';
|
||||
|
@ -286,6 +299,7 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
|
|||
final String name;
|
||||
final String pluginClass;
|
||||
final String dartPluginClass;
|
||||
final String defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
|
@ -296,7 +310,8 @@ class WindowsPlugin extends PluginPlatform implements NativeOrDartPlugin{
|
|||
'name': name,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
|
||||
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass: dartPluginClass,
|
||||
if (defaultPackage != null) kDefaultPackage: defaultPackage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +325,8 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
@required this.name,
|
||||
this.pluginClass,
|
||||
this.dartPluginClass,
|
||||
}) : assert(pluginClass != null || dartPluginClass != null);
|
||||
this.defaultPackage,
|
||||
}) : assert(pluginClass != null || dartPluginClass != null || defaultPackage != null);
|
||||
|
||||
factory LinuxPlugin.fromYaml(String name, YamlMap yaml) {
|
||||
assert(validate(yaml));
|
||||
|
@ -323,6 +339,7 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
name: name,
|
||||
pluginClass: pluginClass,
|
||||
dartPluginClass: yaml[kDartPluginClass] as String,
|
||||
defaultPackage: yaml[kDefaultPackage] as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -330,7 +347,9 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
if (yaml == null) {
|
||||
return false;
|
||||
}
|
||||
return yaml[kPluginClass] is String || yaml[kDartPluginClass] is String;
|
||||
return yaml[kPluginClass] is String ||
|
||||
yaml[kDartPluginClass] is String ||
|
||||
yaml[kDefaultPackage] is String;
|
||||
}
|
||||
|
||||
static const String kConfigKey = 'linux';
|
||||
|
@ -338,6 +357,7 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
final String name;
|
||||
final String pluginClass;
|
||||
final String dartPluginClass;
|
||||
final String defaultPackage;
|
||||
|
||||
@override
|
||||
bool isNative() => pluginClass != null;
|
||||
|
@ -348,7 +368,8 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin {
|
|||
'name': name,
|
||||
if (pluginClass != null) 'class': pluginClass,
|
||||
if (pluginClass != null) 'filename': _filenameForCppClass(pluginClass),
|
||||
if (dartPluginClass != null) 'dartPluginClass': dartPluginClass,
|
||||
if (dartPluginClass != null) kDartPluginClass: dartPluginClass,
|
||||
if (defaultPackage != null) kDefaultPackage: defaultPackage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'base/os.dart';
|
|||
import 'base/platform.dart';
|
||||
import 'base/version.dart';
|
||||
import 'convert.dart';
|
||||
import 'dart/language_version.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'features.dart';
|
||||
import 'globals.dart' as globals;
|
||||
|
@ -36,11 +37,18 @@ class Plugin {
|
|||
@required this.name,
|
||||
@required this.path,
|
||||
@required this.platforms,
|
||||
@required this.defaultPackagePlatforms,
|
||||
@required this.pluginDartClassPlatforms,
|
||||
@required this.dependencies,
|
||||
@required this.isDirectDependency,
|
||||
this.implementsPackage,
|
||||
}) : assert(name != null),
|
||||
assert(path != null),
|
||||
assert(platforms != null),
|
||||
assert(dependencies != null);
|
||||
assert(defaultPackagePlatforms != null),
|
||||
assert(pluginDartClassPlatforms != null),
|
||||
assert(dependencies != null),
|
||||
assert(isDirectDependency != null);
|
||||
|
||||
/// Parses [Plugin] specification from the provided pluginYaml.
|
||||
///
|
||||
|
@ -76,15 +84,30 @@ class Plugin {
|
|||
YamlMap pluginYaml,
|
||||
List<String> dependencies, {
|
||||
@required FileSystem fileSystem,
|
||||
Set<String> appDependencies,
|
||||
}) {
|
||||
final List<String> errors = validatePluginYaml(pluginYaml);
|
||||
if (errors.isNotEmpty) {
|
||||
throwToolExit('Invalid plugin specification $name.\n${errors.join('\n')}');
|
||||
}
|
||||
if (pluginYaml != null && pluginYaml['platforms'] != null) {
|
||||
return Plugin._fromMultiPlatformYaml(name, path, pluginYaml, dependencies, fileSystem);
|
||||
return Plugin._fromMultiPlatformYaml(
|
||||
name,
|
||||
path,
|
||||
pluginYaml,
|
||||
dependencies,
|
||||
fileSystem,
|
||||
appDependencies != null && appDependencies.contains(name),
|
||||
);
|
||||
}
|
||||
return Plugin._fromLegacyYaml(name, path, pluginYaml, dependencies, fileSystem);
|
||||
return Plugin._fromLegacyYaml(
|
||||
name,
|
||||
path,
|
||||
pluginYaml,
|
||||
dependencies,
|
||||
fileSystem,
|
||||
appDependencies != null && appDependencies.contains(name),
|
||||
);
|
||||
}
|
||||
|
||||
factory Plugin._fromMultiPlatformYaml(
|
||||
|
@ -93,6 +116,7 @@ class Plugin {
|
|||
dynamic pluginYaml,
|
||||
List<String> dependencies,
|
||||
FileSystem fileSystem,
|
||||
bool isDirectDependency,
|
||||
) {
|
||||
assert (pluginYaml != null && pluginYaml['platforms'] != null,
|
||||
'Invalid multi-platform plugin specification $name.');
|
||||
|
@ -137,11 +161,47 @@ class Plugin {
|
|||
WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey] as YamlMap);
|
||||
}
|
||||
|
||||
final String defaultPackageForLinux =
|
||||
_getDefaultPackageForPlatform(platformsYaml, LinuxPlugin.kConfigKey);
|
||||
|
||||
final String defaultPackageForMacOS =
|
||||
_getDefaultPackageForPlatform(platformsYaml, MacOSPlugin.kConfigKey);
|
||||
|
||||
final String defaultPackageForWindows =
|
||||
_getDefaultPackageForPlatform(platformsYaml, WindowsPlugin.kConfigKey);
|
||||
|
||||
final String defaultPluginDartClassForLinux =
|
||||
_getPluginDartClassForPlatform(platformsYaml, LinuxPlugin.kConfigKey);
|
||||
|
||||
final String defaultPluginDartClassForMacOS =
|
||||
_getPluginDartClassForPlatform(platformsYaml, MacOSPlugin.kConfigKey);
|
||||
|
||||
final String defaultPluginDartClassForWindows =
|
||||
_getPluginDartClassForPlatform(platformsYaml, WindowsPlugin.kConfigKey);
|
||||
|
||||
return Plugin(
|
||||
name: name,
|
||||
path: path,
|
||||
platforms: platforms,
|
||||
defaultPackagePlatforms: <String, String>{
|
||||
if (defaultPackageForLinux != null)
|
||||
LinuxPlugin.kConfigKey : defaultPackageForLinux,
|
||||
if (defaultPackageForMacOS != null)
|
||||
MacOSPlugin.kConfigKey : defaultPackageForMacOS,
|
||||
if (defaultPackageForWindows != null)
|
||||
WindowsPlugin.kConfigKey : defaultPackageForWindows,
|
||||
},
|
||||
pluginDartClassPlatforms: <String, String>{
|
||||
if (defaultPluginDartClassForLinux != null)
|
||||
LinuxPlugin.kConfigKey : defaultPluginDartClassForLinux,
|
||||
if (defaultPluginDartClassForMacOS != null)
|
||||
MacOSPlugin.kConfigKey : defaultPluginDartClassForMacOS,
|
||||
if (defaultPluginDartClassForWindows != null)
|
||||
WindowsPlugin.kConfigKey : defaultPluginDartClassForWindows,
|
||||
},
|
||||
dependencies: dependencies,
|
||||
isDirectDependency: isDirectDependency,
|
||||
implementsPackage: pluginYaml['implements'] != null ? pluginYaml['implements'] as String : '',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -151,6 +211,7 @@ class Plugin {
|
|||
dynamic pluginYaml,
|
||||
List<String> dependencies,
|
||||
FileSystem fileSystem,
|
||||
bool isDirectDependency,
|
||||
) {
|
||||
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
|
||||
final String pluginClass = pluginYaml['pluginClass'] as String;
|
||||
|
@ -178,7 +239,10 @@ class Plugin {
|
|||
name: name,
|
||||
path: path,
|
||||
platforms: platforms,
|
||||
defaultPackagePlatforms: <String, String>{},
|
||||
pluginDartClassPlatforms: <String, String>{},
|
||||
dependencies: dependencies,
|
||||
isDirectDependency: isDirectDependency,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -295,11 +359,41 @@ class Plugin {
|
|||
return errors;
|
||||
}
|
||||
|
||||
static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) {
|
||||
static bool _supportsPlatform(YamlMap platformsYaml, String platformKey) {
|
||||
if (!platformsYaml.containsKey(platformKey)) {
|
||||
return false;
|
||||
}
|
||||
if ((platformsYaml[platformKey] as YamlMap).containsKey('default_package')) {
|
||||
if (platformsYaml[platformKey] is YamlMap) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static String _getDefaultPackageForPlatform(YamlMap platformsYaml, String platformKey) {
|
||||
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
||||
return null;
|
||||
}
|
||||
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) {
|
||||
return (platformsYaml[platformKey] as YamlMap)[kDefaultPackage] as String;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _getPluginDartClassForPlatform(YamlMap platformsYaml, String platformKey) {
|
||||
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
||||
return null;
|
||||
}
|
||||
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDartPluginClass)) {
|
||||
return (platformsYaml[platformKey] as YamlMap)[kDartPluginClass] as String;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool _providesImplementationForPlatform(YamlMap platformsYaml, String platformKey) {
|
||||
if (!_supportsPlatform(platformsYaml, platformKey)) {
|
||||
return false;
|
||||
}
|
||||
if ((platformsYaml[platformKey] as YamlMap).containsKey(kDefaultPackage)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -308,14 +402,28 @@ class Plugin {
|
|||
final String name;
|
||||
final String path;
|
||||
|
||||
/// The name of the interface package that this plugin implements.
|
||||
/// If [null], this plugin doesn't implement an interface.
|
||||
final String implementsPackage;
|
||||
|
||||
/// The name of the packages this plugin depends on.
|
||||
final List<String> dependencies;
|
||||
|
||||
/// This is a mapping from platform config key to the plugin platform spec.
|
||||
final Map<String, PluginPlatform> platforms;
|
||||
|
||||
/// This is a mapping from platform config key to the default package implementation.
|
||||
final Map<String, String> defaultPackagePlatforms;
|
||||
|
||||
/// This is a mapping from platform config key to the plugin class for the given platform.
|
||||
final Map<String, String> pluginDartClassPlatforms;
|
||||
|
||||
/// Whether this plugin is a direct dependency of the app.
|
||||
/// If [false], the plugin is a dependency of another plugin.
|
||||
final bool isDirectDependency;
|
||||
}
|
||||
|
||||
Plugin _pluginFromPackage(String name, Uri packageRoot) {
|
||||
Plugin _pluginFromPackage(String name, Uri packageRoot, Set<String> appDependencies) {
|
||||
final String pubspecPath = globals.fs.path.fromUri(packageRoot.resolve('pubspec.yaml'));
|
||||
if (!globals.fs.isFileSync(pubspecPath)) {
|
||||
return null;
|
||||
|
@ -344,6 +452,7 @@ Plugin _pluginFromPackage(String name, Uri packageRoot) {
|
|||
flutterConfig['plugin'] as YamlMap,
|
||||
dependencies == null ? <String>[] : <String>[...dependencies.keys.cast<String>()],
|
||||
fileSystem: globals.fs,
|
||||
appDependencies: appDependencies,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -360,7 +469,11 @@ Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = t
|
|||
);
|
||||
for (final Package package in packageConfig.packages) {
|
||||
final Uri packageRoot = package.packageUriRoot.resolve('..');
|
||||
final Plugin plugin = _pluginFromPackage(package.name, packageRoot);
|
||||
final Plugin plugin = _pluginFromPackage(
|
||||
package.name,
|
||||
packageRoot,
|
||||
project.manifest.dependencies,
|
||||
);
|
||||
if (plugin != null) {
|
||||
plugins.add(plugin);
|
||||
}
|
||||
|
@ -368,6 +481,130 @@ Future<List<Plugin>> findPlugins(FlutterProject project, { bool throwOnError = t
|
|||
return plugins;
|
||||
}
|
||||
|
||||
/// Metadata associated with the resolution of a platform interface of a plugin.
|
||||
class PluginInterfaceResolution {
|
||||
PluginInterfaceResolution({
|
||||
@required this.plugin,
|
||||
this.platform,
|
||||
}) : assert(plugin != null);
|
||||
|
||||
/// The plugin.
|
||||
final Plugin plugin;
|
||||
// The name of the platform that this plugin implements.
|
||||
final String platform;
|
||||
|
||||
Map<String, String> toMap() {
|
||||
return <String, String> {
|
||||
'pluginName': plugin.name,
|
||||
'platform': platform,
|
||||
'dartClass': plugin.pluginDartClassPlatforms[platform],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the platform implementation for Dart-only plugins.
|
||||
///
|
||||
/// * If there are multiple direct pub dependencies on packages that implement the
|
||||
/// frontend plugin for the current platform, fail.
|
||||
/// * If there is a single direct dependency on a package that implements the
|
||||
/// frontend plugin for the target platform, this package is the selected implementation.
|
||||
/// * If there is no direct dependency on a package that implements the frontend
|
||||
/// plugin for the target platform, and the frontend plugin has a default implementation
|
||||
/// for the target platform the default implementation is selected.
|
||||
/// * Else fail.
|
||||
///
|
||||
/// For more details, https://flutter.dev/go/federated-plugins.
|
||||
List<PluginInterfaceResolution> resolvePlatformImplementation(
|
||||
List<Plugin> plugins, {
|
||||
bool throwOnPluginPubspecError = true,
|
||||
}) {
|
||||
final List<String> platforms = <String>[
|
||||
LinuxPlugin.kConfigKey,
|
||||
MacOSPlugin.kConfigKey,
|
||||
WindowsPlugin.kConfigKey,
|
||||
];
|
||||
final Map<String, PluginInterfaceResolution> directDependencyResolutions
|
||||
= <String, PluginInterfaceResolution>{};
|
||||
final Map<String, String> defaultImplementations = <String, String>{};
|
||||
bool didFindError = false;
|
||||
|
||||
for (final Plugin plugin in plugins) {
|
||||
for (final String platform in platforms) {
|
||||
// The plugin doesn't implement this platform.
|
||||
if (plugin.platforms[platform] == null &&
|
||||
plugin.defaultPackagePlatforms[platform] == null) {
|
||||
continue;
|
||||
}
|
||||
// The plugin doesn't implement an interface, verify that it has a default implementation.
|
||||
if (plugin.implementsPackage == null || plugin.implementsPackage.isEmpty) {
|
||||
final String defaultImplementation = plugin.defaultPackagePlatforms[platform];
|
||||
if (defaultImplementation == null) {
|
||||
globals.printError(
|
||||
'Plugin `${plugin.name}` doesn\'t implement a plugin interface, nor sets '
|
||||
'a default implementation in pubspec.yaml.\n\n'
|
||||
'To set a default implementation, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' platforms:\n'
|
||||
' $platform:\n'
|
||||
' $kDefaultPackage: <plugin-implementation>\n'
|
||||
'\n'
|
||||
'To implement an interface, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' implements: <plugin-interface>'
|
||||
'\n'
|
||||
);
|
||||
didFindError = true;
|
||||
continue;
|
||||
}
|
||||
defaultImplementations['$platform/${plugin.name}'] = defaultImplementation;
|
||||
continue;
|
||||
}
|
||||
if (plugin.pluginDartClassPlatforms[platform] == null ||
|
||||
plugin.pluginDartClassPlatforms[platform] == 'none') {
|
||||
continue;
|
||||
}
|
||||
final String resolutionKey = '$platform/${plugin.implementsPackage}';
|
||||
if (directDependencyResolutions.containsKey(resolutionKey)) {
|
||||
final PluginInterfaceResolution currResolution = directDependencyResolutions[resolutionKey];
|
||||
if (currResolution.plugin.isDirectDependency && plugin.isDirectDependency) {
|
||||
globals.printError(
|
||||
'Plugin `${plugin.name}` implements an interface for `$platform`, which was already '
|
||||
'implemented by plugin `${currResolution.plugin.name}`.\n'
|
||||
'To fix this issue, remove either dependency from pubspec.yaml.'
|
||||
'\n\n'
|
||||
);
|
||||
didFindError = true;
|
||||
}
|
||||
if (currResolution.plugin.isDirectDependency) {
|
||||
// Use the plugin implementation added by the user as a direct dependency.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
directDependencyResolutions[resolutionKey] = PluginInterfaceResolution(
|
||||
plugin: plugin,
|
||||
platform: platform,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (didFindError && throwOnPluginPubspecError) {
|
||||
throwToolExit('Please resolve the errors');
|
||||
}
|
||||
final List<PluginInterfaceResolution> finalResolution = <PluginInterfaceResolution>[];
|
||||
for (final MapEntry<String, PluginInterfaceResolution> resolution in directDependencyResolutions.entries) {
|
||||
if (resolution.value.plugin.isDirectDependency) {
|
||||
finalResolution.add(resolution.value);
|
||||
} else if (defaultImplementations.containsKey(resolution.key)) {
|
||||
// Pick the default implementation.
|
||||
if (defaultImplementations[resolution.key] == resolution.value.plugin.name) {
|
||||
finalResolution.add(resolution.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return finalResolution;
|
||||
}
|
||||
|
||||
// Key strings for the .flutter-plugins-dependencies file.
|
||||
const String _kFlutterPluginsPluginListKey = 'plugins';
|
||||
const String _kFlutterPluginsNameKey = 'name';
|
||||
|
@ -684,6 +921,63 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
|
|||
);
|
||||
}
|
||||
|
||||
/// Generates the Dart plugin registrant, which allows to bind a platform
|
||||
/// implementation of a Dart only plugin to its interface.
|
||||
/// The new entrypoint wraps [currentMainUri], adds a [_registerPlugins] function,
|
||||
/// and writes the file to [newMainDart].
|
||||
///
|
||||
/// [mainFile] is the main entrypoint file. e.g. /<app>/lib/main.dart.
|
||||
///
|
||||
/// Returns [true] if it's necessary to create a plugin registrant, and
|
||||
/// if the new entrypoint was written to disk.
|
||||
///
|
||||
/// For more details, see https://flutter.dev/go/federated-plugins.
|
||||
Future<bool> generateMainDartWithPluginRegistrant(
|
||||
FlutterProject rootProject,
|
||||
PackageConfig packageConfig,
|
||||
String currentMainUri,
|
||||
File newMainDart,
|
||||
File mainFile,
|
||||
) async {
|
||||
final List<Plugin> plugins = await findPlugins(rootProject);
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(
|
||||
plugins,
|
||||
// TODO(egarciad): Turn this on after fixing the pubspec.yaml of the plugins used in tests.
|
||||
throwOnPluginPubspecError: false,
|
||||
);
|
||||
final LanguageVersion entrypointVersion = determineLanguageVersion(
|
||||
mainFile,
|
||||
packageConfig.packageOf(mainFile.absolute.uri),
|
||||
);
|
||||
final Map<String, dynamic> templateContext = <String, dynamic>{
|
||||
'mainEntrypoint': currentMainUri,
|
||||
'dartLanguageVersion': entrypointVersion.toString(),
|
||||
LinuxPlugin.kConfigKey: <dynamic>[],
|
||||
MacOSPlugin.kConfigKey: <dynamic>[],
|
||||
WindowsPlugin.kConfigKey: <dynamic>[],
|
||||
};
|
||||
bool didFindPlugin = false;
|
||||
for (final PluginInterfaceResolution resolution in resolutions) {
|
||||
assert(templateContext.containsKey(resolution.platform));
|
||||
(templateContext[resolution.platform] as List<dynamic>).add(resolution.toMap());
|
||||
didFindPlugin = true;
|
||||
}
|
||||
if (!didFindPlugin) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
_renderTemplateToFile(
|
||||
_dartPluginRegistryForDesktopTemplate,
|
||||
templateContext,
|
||||
newMainDart.path,
|
||||
);
|
||||
return true;
|
||||
} on FileSystemException catch (error) {
|
||||
throwToolExit('Unable to write ${newMainDart.path}, received error: $error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const String _objcPluginRegistryHeaderTemplate = '''
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
|
@ -777,7 +1071,7 @@ Depends on all your plugins, and provides a function to register them.
|
|||
end
|
||||
''';
|
||||
|
||||
const String _dartPluginRegistryTemplate = '''
|
||||
const String _dartPluginRegistryForWebTemplate = '''
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
@ -799,6 +1093,47 @@ void registerPlugins(Registrar registrar) {
|
|||
}
|
||||
''';
|
||||
|
||||
// TODO(egarciad): Evaluate merging the web and desktop plugin registry templates.
|
||||
const String _dartPluginRegistryForDesktopTemplate = '''
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// @dart = {{dartLanguageVersion}}
|
||||
|
||||
import '{{mainEntrypoint}}' as entrypoint;
|
||||
import 'dart:io'; // ignore: dart_io_import.
|
||||
{{#linux}}
|
||||
import 'package:{{pluginName}}/{{pluginName}}.dart';
|
||||
{{/linux}}
|
||||
{{#macos}}
|
||||
import 'package:{{pluginName}}/{{pluginName}}.dart';
|
||||
{{/macos}}
|
||||
{{#windows}}
|
||||
import 'package:{{pluginName}}/{{pluginName}}.dart';
|
||||
{{/windows}}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _registerPlugins() {
|
||||
if (Platform.isLinux) {
|
||||
{{#linux}}
|
||||
{{dartClass}}.registerWith();
|
||||
{{/linux}}
|
||||
} else if (Platform.isMacOS) {
|
||||
{{#macos}}
|
||||
{{dartClass}}.registerWith();
|
||||
{{/macos}}
|
||||
} else if (Platform.isWindows) {
|
||||
{{#windows}}
|
||||
{{dartClass}}.registerWith();
|
||||
{{/windows}}
|
||||
}
|
||||
}
|
||||
void main() {
|
||||
entrypoint.main();
|
||||
}
|
||||
''';
|
||||
|
||||
const String _cppPluginRegistryHeaderTemplate = '''
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
|
@ -1040,7 +1375,7 @@ Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plug
|
|||
return ErrorHandlingFileSystem.deleteIfExists(file);
|
||||
} else {
|
||||
_renderTemplateToFile(
|
||||
_dartPluginRegistryTemplate,
|
||||
_dartPluginRegistryForWebTemplate,
|
||||
context,
|
||||
filePath,
|
||||
);
|
||||
|
|
|
@ -920,6 +920,7 @@ abstract class ResidentRunner {
|
|||
outputDir: globals.fs.directory(getBuildDirectory()),
|
||||
processManager: globals.processManager,
|
||||
projectDir: globals.fs.currentDirectory,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
_lastBuild = await globals.buildSystem.buildIncremental(
|
||||
const GenerateLocalizationsTarget(),
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'devfs.dart';
|
|||
import 'device.dart';
|
||||
import 'features.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'project.dart';
|
||||
import 'reporting/reporting.dart';
|
||||
import 'resident_devtools_handler.dart';
|
||||
import 'resident_runner.dart';
|
||||
|
@ -314,6 +315,15 @@ class HotRunner extends ResidentRunner {
|
|||
bool enableDevTools = false,
|
||||
String route,
|
||||
}) async {
|
||||
File mainFile = globals.fs.file(mainPath);
|
||||
// `generated_main.dart` contains the Dart plugin registry.
|
||||
final Directory buildDir = FlutterProject.current()
|
||||
.directory
|
||||
.childDirectory(globals.fs.path.join('.dart_tool', 'flutter_build'));
|
||||
final File newMainDart = buildDir?.childFile('generated_main.dart');
|
||||
if (newMainDart != null && newMainDart.existsSync()) {
|
||||
mainFile = newMainDart;
|
||||
}
|
||||
firstBuildTime = DateTime.now();
|
||||
|
||||
final List<Future<bool>> startupTasks = <Future<bool>>[];
|
||||
|
@ -326,7 +336,7 @@ class HotRunner extends ResidentRunner {
|
|||
if (device.generator != null) {
|
||||
startupTasks.add(
|
||||
device.generator.recompile(
|
||||
globals.fs.file(mainPath).uri,
|
||||
mainFile.uri,
|
||||
<Uri>[],
|
||||
// When running without a provided applicationBinary, the tool will
|
||||
// simultaneously run the initial frontend_server compilation and
|
||||
|
|
|
@ -1153,6 +1153,7 @@ abstract class FlutterCommand extends Command<void> {
|
|||
outputDir: globals.fs.directory(getBuildDirectory()),
|
||||
processManager: globals.processManager,
|
||||
projectDir: project.directory,
|
||||
generateDartPluginRegistry: true,
|
||||
);
|
||||
|
||||
await generateLocalizationsSyntheticPackage(
|
||||
|
|
|
@ -69,6 +69,8 @@ Future<void> buildWeb(
|
|||
? null
|
||||
: globals.flutterVersion.engineRevision,
|
||||
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
|
||||
// Web uses a different Dart plugin registry.
|
||||
generateDartPluginRegistry: false,
|
||||
));
|
||||
if (!result.success) {
|
||||
for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
||||
|
|
|
@ -36,6 +36,7 @@ void main() {
|
|||
logger: logger,
|
||||
processManager: globals.processManager,
|
||||
engineVersion: 'invalidEngineVersion',
|
||||
generateDartPluginRegistry: false,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -220,6 +220,39 @@ void main() {
|
|||
);
|
||||
|
||||
expect(plugin.platforms, <String, PluginPlatform>{});
|
||||
expect(plugin.defaultPackagePlatforms, <String, String>{
|
||||
'linux': 'sample_package_linux',
|
||||
'macos': 'sample_package_macos',
|
||||
'windows': 'sample_package_windows',
|
||||
});
|
||||
expect(plugin.pluginDartClassPlatforms, <String, String>{});
|
||||
});
|
||||
|
||||
testWithoutContext('Desktop plugin parsing allows a dartPluginClass field', () {
|
||||
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||
const String pluginYamlRaw =
|
||||
'platforms:\n'
|
||||
' linux:\n'
|
||||
' dartPluginClass: LinuxClass\n'
|
||||
' macos:\n'
|
||||
' dartPluginClass: MacOSClass\n'
|
||||
' windows:\n'
|
||||
' dartPluginClass: WindowsClass\n';
|
||||
|
||||
final YamlMap pluginYaml = loadYaml(pluginYamlRaw) as YamlMap;
|
||||
final Plugin plugin = Plugin.fromYaml(
|
||||
_kTestPluginName,
|
||||
_kTestPluginPath,
|
||||
pluginYaml,
|
||||
const <String>[],
|
||||
fileSystem: fileSystem,
|
||||
);
|
||||
|
||||
expect(plugin.pluginDartClassPlatforms, <String, String>{
|
||||
'linux': 'LinuxClass',
|
||||
'macos': 'MacOSClass',
|
||||
'windows': 'WindowsClass',
|
||||
});
|
||||
});
|
||||
|
||||
testWithoutContext('Plugin parsing throws a fatal error on an empty plugin', () {
|
||||
|
|
|
@ -14,6 +14,8 @@ import 'package:flutter_tools/src/base/platform.dart';
|
|||
import 'package:flutter_tools/src/base/time.dart';
|
||||
import 'package:flutter_tools/src/base/utils.dart';
|
||||
import 'package:flutter_tools/src/features.dart';
|
||||
import 'package:flutter_tools/src/dart/package_map.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/plugins.dart';
|
||||
|
@ -21,6 +23,7 @@ import 'package:flutter_tools/src/project.dart';
|
|||
import 'package:flutter_tools/src/version.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:package_config/package_config.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
|
@ -32,6 +35,7 @@ void main() {
|
|||
group('plugins', () {
|
||||
FileSystem fs;
|
||||
MockFlutterProject flutterProject;
|
||||
MockFlutterManifest flutterManifest;
|
||||
MockIosProject iosProject;
|
||||
MockMacOSProject macosProject;
|
||||
MockAndroidProject androidProject;
|
||||
|
@ -48,6 +52,12 @@ void main() {
|
|||
// Adds basic properties to the flutterProject and its subprojects.
|
||||
void setUpProject(FileSystem fileSystem) {
|
||||
flutterProject = MockFlutterProject();
|
||||
|
||||
flutterManifest = MockFlutterManifest();
|
||||
when(flutterManifest.dependencies).thenReturn(<String>{});
|
||||
|
||||
when(flutterProject.manifest).thenReturn(flutterManifest);
|
||||
|
||||
when(flutterProject.directory).thenReturn(fileSystem.systemTempDirectory.childDirectory('app'));
|
||||
// TODO(franciscojma): Remove logic for .flutter-plugins once it's deprecated.
|
||||
when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.flutter-plugins'));
|
||||
|
@ -1297,6 +1307,767 @@ flutter:
|
|||
});
|
||||
});
|
||||
|
||||
group('resolvePlatformImplementation', () {
|
||||
test('selects implementation from direct dependency', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux',
|
||||
'url_launcher_macos',
|
||||
};
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_macos',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'macos': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginMacOS',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'undirect_dependency_plugin',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'windows': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginWindows',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_macos',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'macos': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginMacOS',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
|
||||
expect(resolutions.length, equals(2));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'url_launcher_linux',
|
||||
'dartClass': 'UrlLauncherPluginLinux',
|
||||
'platform': 'linux',
|
||||
})
|
||||
);
|
||||
expect(resolutions[1].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'url_launcher_macos',
|
||||
'dartClass': 'UrlLauncherPluginMacOS',
|
||||
'platform': 'macos',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('selects default implementation', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{};
|
||||
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
expect(resolutions.length, equals(1));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'url_launcher_linux',
|
||||
'dartClass': 'UrlLauncherPluginLinux',
|
||||
'platform': 'linux',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('selects default implementation if interface is direct dependency', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{'url_launcher'};
|
||||
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
expect(resolutions.length, equals(1));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'url_launcher_linux',
|
||||
'dartClass': 'UrlLauncherPluginLinux',
|
||||
'platform': 'linux',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('selects user selected implementation despites default implementation', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
};
|
||||
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'user_selected_url_launcher_implementation',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
expect(resolutions.length, equals(1));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'user_selected_url_launcher_implementation',
|
||||
'dartClass': 'UrlLauncherPluginLinux',
|
||||
'platform': 'linux',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('selects user selected implementation despites default implementation', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'user_selected_url_launcher_implementation',
|
||||
'url_launcher',
|
||||
};
|
||||
|
||||
final List<PluginInterfaceResolution> resolutions = resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'default_package': 'url_launcher_linux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'user_selected_url_launcher_implementation',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
expect(resolutions.length, equals(1));
|
||||
expect(resolutions[0].toMap(), equals(
|
||||
<String, String>{
|
||||
'pluginName': 'user_selected_url_launcher_implementation',
|
||||
'dartClass': 'UrlLauncherPluginLinux',
|
||||
'platform': 'linux',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('provides error when user selected multiple implementations', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux_1',
|
||||
'url_launcher_linux_2',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_1',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_2',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin `url_launcher_linux_2` implements an interface for `linux`, which was already implemented by plugin `url_launcher_linux_1`.\n'
|
||||
'To fix this issue, remove either dependency from pubspec.yaml.'
|
||||
'\n\n'
|
||||
);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the errors',
|
||||
));
|
||||
});
|
||||
|
||||
testUsingContext('provides all errors when user selected multiple implementations', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux_1',
|
||||
'url_launcher_linux_2',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_1',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_2',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'implements': 'url_launcher',
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin `url_launcher_linux_2` implements an interface for `linux`, which was already implemented by plugin `url_launcher_linux_1`.\n'
|
||||
'To fix this issue, remove either dependency from pubspec.yaml.'
|
||||
'\n\n'
|
||||
);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the errors',
|
||||
));
|
||||
});
|
||||
|
||||
testUsingContext('provides error when plugin pubspec.yaml doesn\'t have "implementation" nor "default_implementation"', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux_1',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux_1',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the errors'
|
||||
));
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin `url_launcher_linux_1` doesn\'t implement a plugin interface, '
|
||||
'nor sets a default implementation in pubspec.yaml.\n\n'
|
||||
'To set a default implementation, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' platforms:\n'
|
||||
' linux:\n'
|
||||
' default_package: <plugin-implementation>\n'
|
||||
'\n'
|
||||
'To implement an interface, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' implements: <plugin-interface>'
|
||||
'\n\n'
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('provides all errors when plugin pubspec.yaml doesn\'t have "implementation" nor "default_implementation"', () async {
|
||||
final FileSystem fs = MemoryFileSystem();
|
||||
final Set<String> directDependencies = <String>{
|
||||
'url_launcher_linux',
|
||||
'url_launcher_windows',
|
||||
};
|
||||
expect(() {
|
||||
resolvePlatformImplementation(<Plugin>[
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_linux',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'linux': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginLinux',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
Plugin.fromYaml(
|
||||
'url_launcher_windows',
|
||||
'',
|
||||
YamlMap.wrap(<String, dynamic>{
|
||||
'platforms': <String, dynamic>{
|
||||
'windows': <String, dynamic>{
|
||||
'dartPluginClass': 'UrlLauncherPluginWindows',
|
||||
},
|
||||
},
|
||||
}),
|
||||
<String>[],
|
||||
fileSystem: fs,
|
||||
appDependencies: directDependencies,
|
||||
),
|
||||
]);
|
||||
},
|
||||
throwsToolExit(
|
||||
message: 'Please resolve the errors'
|
||||
));
|
||||
expect(
|
||||
testLogger.errorText,
|
||||
'Plugin `url_launcher_linux` doesn\'t implement a plugin interface, '
|
||||
'nor sets a default implementation in pubspec.yaml.\n\n'
|
||||
'To set a default implementation, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' platforms:\n'
|
||||
' linux:\n'
|
||||
' default_package: <plugin-implementation>\n'
|
||||
'\n'
|
||||
'To implement an interface, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' implements: <plugin-interface>'
|
||||
'\n\n'
|
||||
'Plugin `url_launcher_windows` doesn\'t implement a plugin interface, '
|
||||
'nor sets a default implementation in pubspec.yaml.\n\n'
|
||||
'To set a default implementation, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' platforms:\n'
|
||||
' windows:\n'
|
||||
' default_package: <plugin-implementation>\n'
|
||||
'\n'
|
||||
'To implement an interface, use:\n'
|
||||
'flutter:\n'
|
||||
' plugin:\n'
|
||||
' implements: <plugin-interface>'
|
||||
'\n\n'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('generateMainDartWithPluginRegistrant', () {
|
||||
testUsingContext('Generates new entrypoint', () async {
|
||||
when(flutterProject.isModule).thenReturn(false);
|
||||
|
||||
final List<Directory> directories = <Directory>[];
|
||||
final Directory fakePubCache = fs.systemTempDirectory.childDirectory('cache');
|
||||
final File packagesFile = flutterProject.directory
|
||||
.childFile('.packages')
|
||||
..createSync(recursive: true);
|
||||
|
||||
final Map<String, String> plugins = <String, String>{};
|
||||
plugins['url_launcher_macos'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
macos:
|
||||
dartPluginClass: MacOSPlugin
|
||||
''';
|
||||
plugins['url_launcher_linux'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
linux:
|
||||
dartPluginClass: LinuxPlugin
|
||||
''';
|
||||
plugins['url_launcher_windows'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
windows:
|
||||
dartPluginClass: WindowsPlugin
|
||||
''';
|
||||
plugins['awesome_macos'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: awesome
|
||||
platforms:
|
||||
macos:
|
||||
dartPluginClass: AwesomeMacOS
|
||||
''';
|
||||
for (final MapEntry<String, String> entry in plugins.entries) {
|
||||
final String name = fs.path.basename(entry.key);
|
||||
final Directory pluginDirectory = fakePubCache.childDirectory(name);
|
||||
packagesFile.writeAsStringSync(
|
||||
'$name:file://${pluginDirectory.childFile('lib').uri}\n',
|
||||
mode: FileMode.writeOnlyAppend);
|
||||
pluginDirectory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(entry.value);
|
||||
directories.add(pluginDirectory);
|
||||
}
|
||||
|
||||
when(flutterManifest.dependencies).thenReturn(<String>{...plugins.keys});
|
||||
|
||||
final Directory libDir = flutterProject.directory.childDirectory('lib');
|
||||
libDir.createSync(recursive: true);
|
||||
|
||||
final File mainFile = libDir.childFile('main.dart');
|
||||
mainFile.writeAsStringSync('''
|
||||
// @dart = 2.8
|
||||
void main() {
|
||||
}
|
||||
''');
|
||||
final File flutterBuild = flutterProject.directory.childFile('generated_main.dart');
|
||||
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
|
||||
flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'),
|
||||
logger: globals.logger,
|
||||
throwOnError: false,
|
||||
);
|
||||
final bool didGenerate = await generateMainDartWithPluginRegistrant(
|
||||
flutterProject,
|
||||
packageConfig,
|
||||
'package:app/main.dart',
|
||||
flutterBuild,
|
||||
mainFile,
|
||||
);
|
||||
expect(didGenerate, isTrue);
|
||||
expect(flutterBuild.readAsStringSync(),
|
||||
'//\n'
|
||||
'// Generated file. Do not edit.\n'
|
||||
'//\n'
|
||||
'\n'
|
||||
'// @dart = 2.8\n'
|
||||
'\n'
|
||||
'import \'package:app/main.dart\' as entrypoint;\n'
|
||||
'import \'dart:io\'; // ignore: dart_io_import.\n'
|
||||
'import \'package:url_launcher_linux${fs.path.separator}url_launcher_linux.dart\';\n'
|
||||
'import \'package:awesome_macos/awesome_macos.dart\';\n'
|
||||
'import \'package:url_launcher_macos${fs.path.separator}url_launcher_macos.dart\';\n'
|
||||
'import \'package:url_launcher_windows${fs.path.separator}url_launcher_windows.dart\';\n'
|
||||
'\n'
|
||||
'@pragma(\'vm:entry-point\')\n'
|
||||
'void _registerPlugins() {\n'
|
||||
' if (Platform.isLinux) {\n'
|
||||
' LinuxPlugin.registerWith();\n'
|
||||
' } else if (Platform.isMacOS) {\n'
|
||||
' AwesomeMacOS.registerWith();\n'
|
||||
' MacOSPlugin.registerWith();\n'
|
||||
' } else if (Platform.isWindows) {\n'
|
||||
' WindowsPlugin.registerWith();\n'
|
||||
' }\n'
|
||||
'}\n'
|
||||
'void main() {\n'
|
||||
' entrypoint.main();\n'
|
||||
'}\n'
|
||||
'',
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Plugin without platform support throws tool exit', () async {
|
||||
when(flutterProject.isModule).thenReturn(false);
|
||||
|
||||
final List<Directory> directories = <Directory>[];
|
||||
final Directory fakePubCache = fs.systemTempDirectory.childDirectory('cache');
|
||||
final File packagesFile = flutterProject.directory
|
||||
.childFile('.packages')
|
||||
..createSync(recursive: true);
|
||||
final Map<String, String> plugins = <String, String>{};
|
||||
plugins['url_launcher_macos'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
platforms:
|
||||
macos:
|
||||
invalid:
|
||||
''';
|
||||
for (final MapEntry<String, String> entry in plugins.entries) {
|
||||
final String name = fs.path.basename(entry.key);
|
||||
final Directory pluginDirectory = fakePubCache.childDirectory(name);
|
||||
packagesFile.writeAsStringSync(
|
||||
'$name:file://${pluginDirectory.childFile('lib').uri}\n',
|
||||
mode: FileMode.writeOnlyAppend);
|
||||
pluginDirectory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(entry.value);
|
||||
directories.add(pluginDirectory);
|
||||
}
|
||||
|
||||
when(flutterManifest.dependencies).thenReturn(<String>{...plugins.keys});
|
||||
|
||||
final Directory libDir = flutterProject.directory.childDirectory('lib');
|
||||
libDir.createSync(recursive: true);
|
||||
|
||||
final File mainFile = libDir.childFile('main.dart')..writeAsStringSync('');
|
||||
final File flutterBuild = flutterProject.directory.childFile('generated_main.dart');
|
||||
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
|
||||
flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'),
|
||||
logger: globals.logger,
|
||||
throwOnError: false,
|
||||
);
|
||||
await expectLater(
|
||||
generateMainDartWithPluginRegistrant(
|
||||
flutterProject,
|
||||
packageConfig,
|
||||
'package:app/main.dart',
|
||||
flutterBuild,
|
||||
mainFile,
|
||||
), throwsToolExit(message:
|
||||
'Invalid plugin specification url_launcher_macos.\n'
|
||||
'Invalid "macos" plugin specification.'
|
||||
),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Plugin with platform support without dart plugin class throws tool exit', () async {
|
||||
when(flutterProject.isModule).thenReturn(false);
|
||||
|
||||
final List<Directory> directories = <Directory>[];
|
||||
final Directory fakePubCache = fs.systemTempDirectory.childDirectory('cache');
|
||||
final File packagesFile = flutterProject.directory
|
||||
.childFile('.packages')
|
||||
..createSync(recursive: true);
|
||||
final Map<String, String> plugins = <String, String>{};
|
||||
plugins['url_launcher_macos'] = '''
|
||||
flutter:
|
||||
plugin:
|
||||
implements: url_launcher
|
||||
''';
|
||||
for (final MapEntry<String, String> entry in plugins.entries) {
|
||||
final String name = fs.path.basename(entry.key);
|
||||
final Directory pluginDirectory = fakePubCache.childDirectory(name);
|
||||
packagesFile.writeAsStringSync(
|
||||
'$name:file://${pluginDirectory.childFile('lib').uri}\n',
|
||||
mode: FileMode.writeOnlyAppend);
|
||||
pluginDirectory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(entry.value);
|
||||
directories.add(pluginDirectory);
|
||||
}
|
||||
|
||||
when(flutterManifest.dependencies).thenReturn(<String>{...plugins.keys});
|
||||
|
||||
final Directory libDir = flutterProject.directory.childDirectory('lib');
|
||||
libDir.createSync(recursive: true);
|
||||
|
||||
final File mainFile = libDir.childFile('main.dart')..writeAsStringSync('');
|
||||
final File flutterBuild = flutterProject.directory.childFile('generated_main.dart');
|
||||
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
|
||||
flutterProject.directory.childDirectory('.dart_tool').childFile('package_config.json'),
|
||||
logger: globals.logger,
|
||||
throwOnError: false,
|
||||
);
|
||||
await expectLater(
|
||||
generateMainDartWithPluginRegistrant(
|
||||
flutterProject,
|
||||
packageConfig,
|
||||
'package:app/main.dart',
|
||||
flutterBuild,
|
||||
mainFile,
|
||||
), throwsToolExit(message:
|
||||
'Invalid plugin specification url_launcher_macos.\n'
|
||||
'Cannot find the `flutter.plugin.platforms` key in the `pubspec.yaml` file. '
|
||||
'An instruction to format the `pubspec.yaml` can be found here: '
|
||||
'https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms'
|
||||
),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
});
|
||||
|
||||
group('pubspec', () {
|
||||
|
||||
Directory projectDir;
|
||||
|
@ -1396,6 +2167,7 @@ flutter:
|
|||
}
|
||||
|
||||
class MockAndroidProject extends Mock implements AndroidProject {}
|
||||
class MockFlutterManifest extends Mock implements FlutterManifest {}
|
||||
class MockFlutterProject extends Mock implements FlutterProject {}
|
||||
class MockIosProject extends Mock implements IosProject {}
|
||||
class MockMacOSProject extends Mock implements MacOSProject {}
|
||||
|
|
|
@ -110,6 +110,17 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
_testInMemory('reads dependencies from pubspec.yaml', () async {
|
||||
final Directory directory = globals.fs.directory('myproject');
|
||||
directory.childFile('pubspec.yaml')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync(validPubspecWithDependencies);
|
||||
expect(
|
||||
FlutterProject.fromDirectory(directory).manifest.dependencies,
|
||||
<String>{'plugin_a', 'plugin_b'},
|
||||
);
|
||||
});
|
||||
|
||||
_testInMemory('sets up location', () async {
|
||||
final Directory directory = globals.fs.directory('myproject');
|
||||
expect(
|
||||
|
@ -905,6 +916,16 @@ name: hello
|
|||
flutter:
|
||||
''';
|
||||
|
||||
String get validPubspecWithDependencies => '''
|
||||
name: hello
|
||||
flutter:
|
||||
|
||||
dependencies:
|
||||
plugin_a:
|
||||
plugin_b:
|
||||
''';
|
||||
|
||||
|
||||
String get invalidPubspec => '''
|
||||
name: hello
|
||||
flutter:
|
||||
|
|
Loading…
Reference in a new issue