mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2892 lines
100 KiB
Dart
2892 lines
100 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:file_testing/file_testing.dart';
|
|
import 'package:flutter_tools/src/base/error_handling_io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/os.dart';
|
|
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/dart/pub.dart';
|
|
import 'package:flutter_tools/src/darwin/darwin.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/flutter_manifest.dart';
|
|
import 'package:flutter_tools/src/flutter_plugins.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/macos/cocoapods.dart';
|
|
import 'package:flutter_tools/src/macos/darwin_dependency_management.dart';
|
|
import 'package:flutter_tools/src/platform_plugins.dart';
|
|
import 'package:flutter_tools/src/plugins.dart';
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:flutter_tools/src/version.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fakes.dart' hide FakeOperatingSystemUtils;
|
|
import '../src/package_config.dart';
|
|
import '../src/pubspec_schema.dart';
|
|
import '../src/throwing_pub.dart';
|
|
|
|
/// Information for a platform entry in the 'platforms' section of a plugin's
|
|
/// pubspec.yaml.
|
|
class _PluginPlatformInfo {
|
|
const _PluginPlatformInfo({
|
|
this.pluginClass,
|
|
this.dartPluginClass,
|
|
this.androidPackage,
|
|
this.sharedDarwinSource = false,
|
|
this.fileName,
|
|
}) : assert(pluginClass != null || dartPluginClass != null),
|
|
assert(androidPackage == null || pluginClass != null);
|
|
|
|
/// The pluginClass entry, if any.
|
|
final String? pluginClass;
|
|
|
|
/// The dartPluginClass entry, if any.
|
|
final String? dartPluginClass;
|
|
|
|
/// The package entry for an Android plugin implementation using pluginClass.
|
|
final String? androidPackage;
|
|
|
|
final bool sharedDarwinSource;
|
|
|
|
/// The fileName entry for a web plugin implementation.
|
|
final String? fileName;
|
|
|
|
/// Returns the body of a platform section for a plugin's pubspec, properly
|
|
/// indented.
|
|
String get indentedPubspecSection {
|
|
const indentation = ' ';
|
|
return <String>[
|
|
if (pluginClass != null) '${indentation}pluginClass: $pluginClass',
|
|
if (dartPluginClass != null) '${indentation}dartPluginClass: $dartPluginClass',
|
|
if (androidPackage != null) '${indentation}package: $androidPackage',
|
|
if (sharedDarwinSource) '${indentation}sharedDarwinSource: true',
|
|
if (fileName != null) '${indentation}fileName: $fileName',
|
|
].join('\n');
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
group('plugins', () {
|
|
late FileSystem fs;
|
|
late FakeFlutterProject flutterProject;
|
|
late FakeFlutterManifest flutterManifest;
|
|
late FakeIosProject iosProject;
|
|
late FakeMacOSProject macosProject;
|
|
late FakeAndroidProject androidProject;
|
|
late FakeWebProject webProject;
|
|
late FakeWindowsProject windowsProject;
|
|
late FakeLinuxProject linuxProject;
|
|
late FakeSystemClock systemClock;
|
|
late FlutterVersion flutterVersion;
|
|
late FakeCocoaPodsCapturesInvalidate cocoaPods;
|
|
|
|
// A Windows-style filesystem. This is not populated by default, so tests
|
|
// using it instead of fs must re-run any necessary setup (e.g.,
|
|
// setUpProject).
|
|
late FileSystem fsWindows;
|
|
const pubCachePath = '/path/to/.pub-cache/hosted/pub.dartlang.org/foo-1.2.3';
|
|
const ephemeralPackagePath = '/path/to/app/linux/flutter/ephemeral/foo-1.2.3';
|
|
|
|
// Adds basic properties to the flutterProject and its subprojects.
|
|
void setUpProject(FileSystem fileSystem) {
|
|
flutterProject = FakeFlutterProject();
|
|
flutterManifest = FakeFlutterManifest();
|
|
flutterManifest.appName = 'my_app';
|
|
|
|
flutterProject
|
|
..manifest = flutterManifest
|
|
..directory = fileSystem.systemTempDirectory.childDirectory('app')
|
|
..flutterPluginsDependenciesFile = flutterProject.directory.childFile(
|
|
'.flutter-plugins-dependencies',
|
|
);
|
|
|
|
iosProject = FakeIosProject();
|
|
flutterProject.ios = iosProject;
|
|
final Directory iosDirectory = flutterProject.directory.childDirectory('ios');
|
|
iosProject
|
|
..pluginRegistrantHost = flutterProject.directory.childDirectory('Runner')
|
|
..podfile = iosDirectory.childFile('Podfile')
|
|
..podManifestLock = iosDirectory.childFile('Podfile.lock')
|
|
..testExists = false;
|
|
|
|
macosProject = FakeMacOSProject();
|
|
flutterProject.macos = macosProject;
|
|
final Directory macosDirectory = flutterProject.directory.childDirectory('macos');
|
|
final Directory macosManagedDirectory = macosDirectory.childDirectory('Flutter');
|
|
macosProject
|
|
..podfile = macosDirectory.childFile('Podfile')
|
|
..podManifestLock = macosDirectory.childFile('Podfile.lock')
|
|
..managedDirectory = macosManagedDirectory
|
|
..exists = false;
|
|
|
|
androidProject = FakeAndroidProject();
|
|
flutterProject.android = androidProject;
|
|
final Directory androidDirectory = flutterProject.directory.childDirectory('android');
|
|
androidProject
|
|
..pluginRegistrantHost = androidDirectory.childDirectory('app')
|
|
..hostAppGradleRoot = androidDirectory
|
|
..exists = false
|
|
..embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
webProject = FakeWebProject();
|
|
flutterProject.web = webProject;
|
|
webProject
|
|
..libDirectory = flutterProject.directory.childDirectory('lib')
|
|
..exists = false;
|
|
|
|
windowsProject = FakeWindowsProject();
|
|
flutterProject.windows = windowsProject;
|
|
final Directory windowsManagedDirectory = flutterProject.directory
|
|
.childDirectory('windows')
|
|
.childDirectory('flutter');
|
|
windowsProject
|
|
..managedDirectory = windowsManagedDirectory
|
|
..cmakeFile = windowsManagedDirectory.parent.childFile('CMakeLists.txt')
|
|
..generatedPluginCmakeFile = windowsManagedDirectory.childFile('generated_plugins.mk')
|
|
..pluginSymlinkDirectory = windowsManagedDirectory
|
|
.childDirectory('ephemeral')
|
|
.childDirectory('.plugin_symlinks')
|
|
..exists = false;
|
|
|
|
linuxProject = FakeLinuxProject();
|
|
flutterProject.linux = linuxProject;
|
|
final Directory linuxManagedDirectory = flutterProject.directory
|
|
.childDirectory('linux')
|
|
.childDirectory('flutter');
|
|
final Directory linuxEphemeralDirectory = linuxManagedDirectory.childDirectory('ephemeral');
|
|
linuxProject
|
|
..managedDirectory = linuxManagedDirectory
|
|
..ephemeralDirectory = linuxEphemeralDirectory
|
|
..pluginSymlinkDirectory = linuxEphemeralDirectory.childDirectory('.plugin_symlinks')
|
|
..cmakeFile = linuxManagedDirectory.parent.childFile('CMakeLists.txt')
|
|
..generatedPluginCmakeFile = linuxManagedDirectory.childFile('generated_plugins.mk')
|
|
..exists = false;
|
|
}
|
|
|
|
setUp(() async {
|
|
fs = MemoryFileSystem.test();
|
|
fsWindows = MemoryFileSystem(style: FileSystemStyle.windows);
|
|
systemClock = FakeSystemClock()..currentTime = DateTime(1970);
|
|
flutterVersion = FakeFlutterVersion(frameworkVersion: '1.0.0');
|
|
cocoaPods = FakeCocoaPodsCapturesInvalidate();
|
|
|
|
// Add basic properties to the Flutter project and subprojects
|
|
setUpProject(fs);
|
|
writePackageConfigFiles(directory: flutterProject.directory, mainLibName: 'my_app');
|
|
});
|
|
|
|
void addToPackageConfig(String name, Directory packageDir, {bool isDevDependency = false}) {
|
|
final File packageConfigFile = flutterProject.packageConfig;
|
|
|
|
final packageConfig =
|
|
jsonDecode(packageConfigFile.readAsStringSync()) as Map<String, Object?>;
|
|
|
|
(packageConfig['packages']! as List<Object?>).add(<String, Object?>{
|
|
'name': name,
|
|
'rootUri': packageDir.uri.toString(),
|
|
'packageUri': 'lib/',
|
|
});
|
|
|
|
packageConfigFile.writeAsStringSync(jsonEncode(packageConfig));
|
|
|
|
final File packageGraphFile = flutterProject.packageConfig.parent.childFile(
|
|
'package_graph.json',
|
|
);
|
|
|
|
final packageGraph = jsonDecode(packageGraphFile.readAsStringSync()) as Map<String, Object?>;
|
|
|
|
final packages = packageGraph['packages']! as List<Object?>;
|
|
|
|
packages.add(<String, Object?>{'name': name, 'dependencies': <Object?>[]});
|
|
|
|
final mainPackage =
|
|
packages.firstWhere(
|
|
(Object? p) =>
|
|
(p! as Map<String, Object?>)['name'] == flutterProject.manifest.appName,
|
|
)!
|
|
as Map<String, Object?>;
|
|
final dependencyList =
|
|
(mainPackage[isDevDependency ? 'devDependencies' : 'dependencies'] ??= <Object?>[])
|
|
as List<Object?>;
|
|
|
|
dependencyList.add(name);
|
|
|
|
packageGraphFile.writeAsStringSync(jsonEncode(packageGraph));
|
|
}
|
|
|
|
// Makes fake plugin packages for each plugin, adds them to flutterProject,
|
|
// and returns their directories.
|
|
//
|
|
// If an entry contains a path separator, it will be treated as a path for
|
|
// the location of the package, with the name being the last component.
|
|
// Otherwise it will be treated as a name, and put in a default location
|
|
// (a fake pub cache).
|
|
List<Directory> createFakePlugins(FileSystem fileSystem, List<String> pluginNamesOrPaths) {
|
|
const pluginYamlTemplate = '''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
ios:
|
|
pluginClass: PLUGIN_CLASS
|
|
macos:
|
|
pluginClass: PLUGIN_CLASS
|
|
windows:
|
|
pluginClass: PLUGIN_CLASS
|
|
linux:
|
|
pluginClass: PLUGIN_CLASS
|
|
web:
|
|
pluginClass: PLUGIN_CLASS
|
|
fileName: lib/PLUGIN_CLASS.dart
|
|
android:
|
|
pluginClass: PLUGIN_CLASS
|
|
package: AndroidPackage
|
|
''';
|
|
|
|
final directories = <Directory>[];
|
|
final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
|
|
writePackageConfigFiles(directory: flutterProject.directory, mainLibName: 'my_app');
|
|
for (final nameOrPath in pluginNamesOrPaths) {
|
|
final String name = fileSystem.path.basename(nameOrPath);
|
|
final Directory pluginDirectory = (nameOrPath == name)
|
|
? fakePubCache.childDirectory(name)
|
|
: fileSystem.directory(nameOrPath);
|
|
addToPackageConfig(name, pluginDirectory);
|
|
pluginDirectory.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(
|
|
pluginYamlTemplate.replaceAll('PLUGIN_CLASS', sentenceCase(camelCase(name))),
|
|
);
|
|
directories.add(pluginDirectory);
|
|
}
|
|
return directories;
|
|
}
|
|
|
|
// Makes a fake plugin package, adds it to flutterProject, and returns its directory.
|
|
Directory createFakePlugin(FileSystem fileSystem) {
|
|
return createFakePlugins(fileSystem, <String>['some_plugin'])[0];
|
|
}
|
|
|
|
void createNewJavaPlugin1() {
|
|
final Directory pluginUsingJavaAndNewEmbeddingDir = fs.systemTempDirectory.createTempSync(
|
|
'flutter_plugin_using_java_and_new_embedding_dir.',
|
|
);
|
|
pluginUsingJavaAndNewEmbeddingDir.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
androidPackage: plugin1
|
|
pluginClass: UseNewEmbedding
|
|
''');
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
.childDirectory('android')
|
|
.childDirectory('src')
|
|
.childDirectory('main')
|
|
.childDirectory('java')
|
|
.childDirectory('plugin1')
|
|
.childFile('UseNewEmbedding.java')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
|
|
addToPackageConfig('plugin1', pluginUsingJavaAndNewEmbeddingDir);
|
|
}
|
|
|
|
Directory 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;');
|
|
|
|
addToPackageConfig('plugin1', pluginUsingJavaAndNewEmbeddingDir);
|
|
return pluginUsingJavaAndNewEmbeddingDir;
|
|
}
|
|
|
|
void createDualSupportJavaPlugin4() {
|
|
final Directory pluginUsingJavaAndNewEmbeddingDir = fs.systemTempDirectory.createTempSync(
|
|
'flutter_plugin_using_java_and_new_embedding_dir.',
|
|
);
|
|
pluginUsingJavaAndNewEmbeddingDir.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
androidPackage: plugin4
|
|
pluginClass: UseBothEmbedding
|
|
''');
|
|
pluginUsingJavaAndNewEmbeddingDir
|
|
.childDirectory('android')
|
|
.childDirectory('src')
|
|
.childDirectory('main')
|
|
.childDirectory('java')
|
|
.childDirectory('plugin4')
|
|
.childFile('UseBothEmbedding.java')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(
|
|
'import io.flutter.embedding.engine.plugins.FlutterPlugin;\n'
|
|
'PluginRegistry\n'
|
|
'registerWith(Irrelevant registrar)\n',
|
|
);
|
|
|
|
addToPackageConfig('plugin4', pluginUsingJavaAndNewEmbeddingDir);
|
|
}
|
|
|
|
Directory createLegacyPluginWithDependencies({
|
|
required String name,
|
|
required List<String> dependencies,
|
|
}) {
|
|
final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('flutter_plugin.');
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
name: $name
|
|
flutter:
|
|
plugin:
|
|
androidPackage: plugin2
|
|
pluginClass: UseNewEmbedding
|
|
dependencies:
|
|
''');
|
|
for (final dependency in dependencies) {
|
|
pluginDirectory
|
|
.childFile('pubspec.yaml')
|
|
.writeAsStringSync(' $dependency:\n', mode: FileMode.append);
|
|
}
|
|
addToPackageConfig(name, pluginDirectory);
|
|
return pluginDirectory;
|
|
}
|
|
|
|
Directory createPlugin({
|
|
required String name,
|
|
required Map<String, _PluginPlatformInfo> platforms,
|
|
List<String> dependencies = const <String>[],
|
|
bool isDevDependency = false,
|
|
}) {
|
|
final Iterable<String> platformSections = platforms.entries.map(
|
|
(MapEntry<String, _PluginPlatformInfo> entry) =>
|
|
'''
|
|
${entry.key}:
|
|
${entry.value.indentedPubspecSection}
|
|
''',
|
|
);
|
|
final Directory pluginDirectory = fs.systemTempDirectory.createTempSync('flutter_plugin.');
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
name: $name
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
${platformSections.join('\n')}
|
|
|
|
dependencies:
|
|
''');
|
|
for (final dependency in dependencies) {
|
|
pluginDirectory
|
|
.childFile('pubspec.yaml')
|
|
.writeAsStringSync(' $dependency:\n', mode: FileMode.append);
|
|
}
|
|
addToPackageConfig(name, pluginDirectory, isDevDependency: isDevDependency);
|
|
return pluginDirectory;
|
|
}
|
|
|
|
// Creates the files that would indicate that pod install has run for the
|
|
// given project.
|
|
void simulatePodInstallRun(XcodeBasedProject project) {
|
|
project.podManifestLock.createSync(recursive: true);
|
|
}
|
|
|
|
group('refreshPlugins', () {
|
|
testUsingContext(
|
|
'Refreshing the plugin list is a no-op when the plugins list stays empty',
|
|
() async {
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, isNot(exists));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Refreshing the plugin list deletes the plugin file when there were plugins but no longer are',
|
|
() async {
|
|
flutterProject.flutterPluginsDependenciesFile.createSync();
|
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, isNot(exists));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Refreshing the plugin list creates a sorted plugin directory when there are plugins',
|
|
() async {
|
|
createFakePlugins(fs, <String>[
|
|
'plugin_d',
|
|
'plugin_a',
|
|
'/local_plugins/plugin_c',
|
|
'/local_plugins/plugin_b',
|
|
]);
|
|
|
|
iosProject.testExists = true;
|
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
|
|
final String pluginsFileContents = flutterProject.flutterPluginsDependenciesFile
|
|
.readAsStringSync();
|
|
expect(
|
|
pluginsFileContents.indexOf('plugin_a'),
|
|
lessThan(pluginsFileContents.indexOf('plugin_b')),
|
|
);
|
|
expect(
|
|
pluginsFileContents.indexOf('plugin_b'),
|
|
lessThan(pluginsFileContents.indexOf('plugin_c')),
|
|
);
|
|
expect(
|
|
pluginsFileContents.indexOf('plugin_c'),
|
|
lessThan(pluginsFileContents.indexOf('plugin_d')),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Refreshing the plugin list updates .flutter-plugins-dependencies if the plugins changed',
|
|
() async {
|
|
// Refresh the plugin list (we have no plugins).
|
|
await refreshPluginsList(flutterProject);
|
|
expect(flutterProject.flutterPluginsDependenciesFile, isNot(exists));
|
|
|
|
// Create an initial plugin (we previously had none).
|
|
createLegacyPluginWithDependencies(name: 'plugin-a', dependencies: <String>[]);
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final FileStat stat1 = flutterProject.flutterPluginsDependenciesFile.statSync();
|
|
|
|
// Add a new plugin.
|
|
createLegacyPluginWithDependencies(name: 'plugin-b', dependencies: <String>[]);
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final FileStat stat2 = flutterProject.flutterPluginsDependenciesFile.statSync();
|
|
expect(
|
|
stat2.modified.isAfter(stat1.modified),
|
|
isTrue,
|
|
reason: 'A new plugin was added, .flutter-plugins-dependencies file should be updated.',
|
|
);
|
|
|
|
// Do not add new plugins.
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final FileStat stat3 = flutterProject.flutterPluginsDependenciesFile.statSync();
|
|
expect(
|
|
stat3.modified,
|
|
stat2.modified,
|
|
reason: 'No plugins changed, .flutter-plugins-dependencies should not be changed',
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
// TODO(matanlurey): Remove as part of https://github.com/flutter/flutter/issues/160257.
|
|
// Not necessary, you can observe this bug by calling `generateLegacyPlugins: false`,
|
|
// but since this flag is about to be enabled, and enabling it implicitly sets that
|
|
// argument to false, this is a more "honest" test.
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Refreshing the plugin list for iOS/macOS projects invokes invalidatePodInstallOutput if the plugins changed',
|
|
() async {
|
|
// Refresh the plugin list (we have no plugins).
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(
|
|
cocoaPods.capturedInvocations,
|
|
isEmpty,
|
|
reason: 'No plugins exist, so no invalidatePodInstallOutput calls expected.',
|
|
);
|
|
|
|
// Create an initial plugin (we previously had none).
|
|
createPlugin(
|
|
name: 'plugin-a',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
},
|
|
);
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(
|
|
cocoaPods.capturedInvocations,
|
|
containsAll(<Matcher>[isA<IosProject>(), isA<MacOSProject>()]),
|
|
reason: 'A new plugin was added, so it should cause invalidatePodInstallOutput calls.',
|
|
);
|
|
cocoaPods.capturedInvocations.clear();
|
|
|
|
// Add a new plugin.
|
|
createPlugin(
|
|
name: 'plugin-b',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
},
|
|
);
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(
|
|
cocoaPods.capturedInvocations,
|
|
containsAll(<Matcher>[isA<IosProject>(), isA<MacOSProject>()]),
|
|
reason: 'A new plugin was added, so it should cause invalidatePodInstallOutput calls.',
|
|
);
|
|
cocoaPods.capturedInvocations.clear();
|
|
|
|
// Do not add new plugins.
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(
|
|
cocoaPods.capturedInvocations,
|
|
isEmpty,
|
|
reason: 'No plugins changed, so no updates expected',
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
CocoaPods: () => cocoaPods,
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
// TODO(matanlurey): Remove as part of https://github.com/flutter/flutter/issues/160257.
|
|
// Not necessary, you can observe this bug by calling `generateLegacyPlugins: false`,
|
|
// but since this flag is about to be enabled, and enabling it implicitly sets that
|
|
// argument to false, this is a more "honest" test.
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'.flutter-plugins-dependencies contains plugin platform info',
|
|
() async {
|
|
createPlugin(
|
|
name: 'plugin-a',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
// Native-only; should include native build.
|
|
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
|
// Hybrid native and Dart; should include native build.
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
// Web; should not have the native build key at all since it doesn't apply.
|
|
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
|
// Dart-only; should not include native build.
|
|
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
|
},
|
|
);
|
|
iosProject.testExists = true;
|
|
|
|
final dateCreated = DateTime(1970);
|
|
systemClock.currentTime = dateCreated;
|
|
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
|
.readAsStringSync();
|
|
final jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
|
final actualPlugins = jsonContent['plugins'] as Map<String, dynamic>?;
|
|
|
|
final expectedPlugins = <String, Object>{
|
|
'ios': <Map<String, Object>>[
|
|
<String, Object>{
|
|
'name': 'plugin-a',
|
|
'path': '/.tmp_rand0/flutter_plugin.rand0/',
|
|
'shared_darwin_source': true,
|
|
'native_build': true,
|
|
'dependencies': <String>[],
|
|
'dev_dependency': false,
|
|
},
|
|
],
|
|
'android': <Map<String, Object>>[
|
|
<String, Object>{
|
|
'name': 'plugin-a',
|
|
'path': '/.tmp_rand0/flutter_plugin.rand0/',
|
|
'native_build': true,
|
|
'dependencies': <String>[],
|
|
'dev_dependency': false,
|
|
},
|
|
],
|
|
'macos': <Map<String, Object>>[],
|
|
'linux': <Map<String, Object>>[],
|
|
'windows': <Map<String, Object>>[
|
|
<String, Object>{
|
|
'name': 'plugin-a',
|
|
'path': '/.tmp_rand0/flutter_plugin.rand0/',
|
|
'native_build': false,
|
|
'dependencies': <String>[],
|
|
'dev_dependency': false,
|
|
},
|
|
],
|
|
'web': <Map<String, Object>>[
|
|
<String, Object>{
|
|
'name': 'plugin-a',
|
|
'path': '/.tmp_rand0/flutter_plugin.rand0/',
|
|
'dependencies': <String>[],
|
|
'dev_dependency': false,
|
|
},
|
|
],
|
|
};
|
|
expect(actualPlugins, expectedPlugins);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'.flutter-plugins-dependencies contains swift_package_manager_enabled true when project is using Swift Package Manager',
|
|
() async {
|
|
createPlugin(
|
|
name: 'plugin-a',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
// Native-only; should include native build.
|
|
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
|
// Hybrid native and Dart; should include native build.
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
// Web; should not have the native build key at all since it doesn't apply.
|
|
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
|
// Dart-only; should not include native build.
|
|
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
|
},
|
|
);
|
|
iosProject.testExists = true;
|
|
|
|
final dateCreated = DateTime(1970);
|
|
systemClock.currentTime = dateCreated;
|
|
|
|
iosProject.usesSwiftPackageManager = true;
|
|
macosProject.usesSwiftPackageManager = true;
|
|
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
|
.readAsStringSync();
|
|
final jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
|
|
|
final expectedSwiftPackageManagerEnabled = <String, dynamic>{'ios': true, 'macos': true};
|
|
expect(jsonContent['swift_package_manager_enabled'], expectedSwiftPackageManagerEnabled);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'.flutter-plugins-dependencies contains swift_package_manager_enabled false when project is using Swift Package Manager but forceCocoaPodsOnly is true',
|
|
() async {
|
|
createPlugin(
|
|
name: 'plugin-a',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
// Native-only; should include native build.
|
|
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
|
// Hybrid native and Dart; should include native build.
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
// Web; should not have the native build key at all since it doesn't apply.
|
|
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
|
// Dart-only; should not include native build.
|
|
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
|
},
|
|
);
|
|
iosProject.testExists = true;
|
|
|
|
final dateCreated = DateTime(1970);
|
|
systemClock.currentTime = dateCreated;
|
|
|
|
iosProject.usesSwiftPackageManager = true;
|
|
macosProject.usesSwiftPackageManager = true;
|
|
|
|
await refreshPluginsList(flutterProject, forceCocoaPodsOnly: true);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
|
.readAsStringSync();
|
|
final jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
|
|
|
final expectedSwiftPackageManagerEnabled = <String, dynamic>{
|
|
'ios': false,
|
|
'macos': false,
|
|
};
|
|
expect(jsonContent['swift_package_manager_enabled'], expectedSwiftPackageManagerEnabled);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'.flutter-plugins-dependencies can have different swift_package_manager_enabled values for iOS and macoS',
|
|
() async {
|
|
createPlugin(
|
|
name: 'plugin-a',
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
// Native-only; should include native build.
|
|
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
|
// Hybrid native and Dart; should include native build.
|
|
'ios': _PluginPlatformInfo(
|
|
pluginClass: 'Foo',
|
|
dartPluginClass: 'Bar',
|
|
sharedDarwinSource: true,
|
|
),
|
|
// Web; should not have the native build key at all since it doesn't apply.
|
|
'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'),
|
|
// Dart-only; should not include native build.
|
|
'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'),
|
|
},
|
|
);
|
|
iosProject.testExists = true;
|
|
|
|
final dateCreated = DateTime(1970);
|
|
systemClock.currentTime = dateCreated;
|
|
|
|
iosProject.usesSwiftPackageManager = true;
|
|
macosProject.usesSwiftPackageManager = false;
|
|
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
|
|
expect(flutterProject.flutterPluginsDependenciesFile, exists);
|
|
final String pluginsString = flutterProject.flutterPluginsDependenciesFile
|
|
.readAsStringSync();
|
|
final jsonContent = json.decode(pluginsString) as Map<String, dynamic>;
|
|
|
|
final expectedSwiftPackageManagerEnabled = <String, dynamic>{'ios': true, 'macos': false};
|
|
expect(jsonContent['swift_package_manager_enabled'], expectedSwiftPackageManagerEnabled);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Changes to the plugin list invalidates the Cocoapod lockfiles',
|
|
() async {
|
|
simulatePodInstallRun(iosProject);
|
|
simulatePodInstallRun(macosProject);
|
|
createFakePlugin(fs);
|
|
iosProject.testExists = true;
|
|
macosProject.exists = true;
|
|
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
expect(iosProject.podManifestLock, isNot(exists));
|
|
expect(macosProject.podManifestLock, isNot(exists));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'No changes to the plugin list does not invalidate the Cocoapod lockfiles',
|
|
() async {
|
|
createFakePlugin(fs);
|
|
iosProject.testExists = true;
|
|
macosProject.exists = true;
|
|
|
|
// First call will create the .flutter-plugins-dependencies and the legacy .flutter-plugins file.
|
|
// Since there was no plugins list, the lock files will be invalidated.
|
|
// The second call is where the plugins list is compared to the existing one, and if there is no change,
|
|
// the podfiles shouldn't be invalidated.
|
|
await refreshPluginsList(flutterProject, iosPlatform: true, macOSPlatform: true);
|
|
simulatePodInstallRun(iosProject);
|
|
simulatePodInstallRun(macosProject);
|
|
|
|
await refreshPluginsList(flutterProject);
|
|
expect(iosProject.podManifestLock, exists);
|
|
expect(macosProject.podManifestLock, exists);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
SystemClock: () => systemClock,
|
|
FlutterVersion: () => flutterVersion,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
});
|
|
|
|
group('injectPlugins', () {
|
|
FakeXcodeProjectInterpreter? xcodeProjectInterpreter;
|
|
|
|
setUp(() {
|
|
xcodeProjectInterpreter = FakeXcodeProjectInterpreter();
|
|
});
|
|
|
|
testUsingContext(
|
|
'Registrant uses new embedding if app uses new embedding',
|
|
() async {
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrant, exists);
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// Issue: https://github.com/flutter/flutter/issues/47803
|
|
testUsingContext(
|
|
'exits the tool if a plugin sets an invalid android package in pubspec.yaml',
|
|
() async {
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v1;
|
|
|
|
final Directory pluginDir = createPluginWithInvalidAndroidPackage();
|
|
|
|
await expectLater(
|
|
() async {
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
},
|
|
throwsToolExit(
|
|
message:
|
|
"The plugin `plugin1` doesn't have a main class defined in "
|
|
'${pluginDir.path}/android/src/main/java/plugin1/invalid/UseNewEmbedding.java or '
|
|
'${pluginDir.path}/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(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'new embedding app uses a plugin that supports v1 and v2 embedding',
|
|
() async {
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
createDualSupportJavaPlugin4();
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrant, exists);
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Modules use new embedding',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrant, exists);
|
|
expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
|
|
expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('public static void registerWith(@NonNull FlutterEngine flutterEngine)'),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Module using new plugin shows no warnings',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
createNewJavaPlugin1();
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'),
|
|
);
|
|
|
|
expect(testLogger.errorText, isNot(contains('go/android-plugin-migration')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Module using plugin with v1 and v2 support shows no warning',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
createDualSupportJavaPlugin4();
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'),
|
|
);
|
|
|
|
expect(testLogger.errorText, isNot(contains('go/android-plugin-migration')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'App using plugin with v1 and v2 support shows no warning',
|
|
() async {
|
|
flutterProject.isModule = false;
|
|
androidProject.embeddingVersion = AndroidEmbeddingVersion.v2;
|
|
|
|
createDualSupportJavaPlugin4();
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory(
|
|
fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'),
|
|
)
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains('flutterEngine.getPlugins().add(new plugin4.UseBothEmbedding());'),
|
|
);
|
|
|
|
expect(testLogger.errorText, isNot(contains('go/android-plugin-migration')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
XcodeProjectInterpreter: () => xcodeProjectInterpreter,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Does not throw when AndroidManifest.xml is not found',
|
|
() async {
|
|
final File manifest = fs.file('AndroidManifest.xml');
|
|
androidProject.appManifestFile = manifest;
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
group('Build time plugin injection', () {
|
|
testUsingContext(
|
|
"Registrant for web doesn't escape slashes in imports",
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
final Directory webPluginWithNestedFile = fs.systemTempDirectory.createTempSync(
|
|
'flutter_web_plugin_with_nested.',
|
|
);
|
|
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
web:
|
|
pluginClass: WebPlugin
|
|
fileName: src/web_plugin.dart
|
|
''');
|
|
webPluginWithNestedFile
|
|
.childDirectory('lib')
|
|
.childDirectory('src')
|
|
.childFile('web_plugin.dart')
|
|
.createSync(recursive: true);
|
|
|
|
addToPackageConfig('web_plugin_with_nested', webPluginWithNestedFile);
|
|
|
|
final Directory destination = flutterProject.directory.childDirectory('lib');
|
|
await injectBuildTimePluginFilesForWebPlatform(
|
|
flutterProject,
|
|
destination: destination,
|
|
);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory('lib')
|
|
.childFile('web_plugin_registrant.dart');
|
|
|
|
expect(registrant, exists);
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains("import 'package:web_plugin_with_nested/src/web_plugin.dart';"),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'user-selected implementation overrides inline implementation on web',
|
|
() async {
|
|
final List<Directory> directories = createFakePlugins(fs, <String>[
|
|
'user_selected_url_launcher_implementation',
|
|
'url_launcher',
|
|
]);
|
|
|
|
// Add inline web implementation to `user_selected_url_launcher_implementation`
|
|
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
implements: url_launcher
|
|
platforms:
|
|
web:
|
|
pluginClass: UserSelectedUrlLauncherWeb
|
|
fileName: src/web_plugin.dart
|
|
''');
|
|
|
|
// Add inline native implementation to `url_launcher`
|
|
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
web:
|
|
pluginClass: InlineUrlLauncherWeb
|
|
fileName: src/web_plugin.dart
|
|
''');
|
|
|
|
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
|
name: my_app
|
|
version: 1.0.0
|
|
|
|
dependencies:
|
|
url_launcher: ^1.0.0
|
|
user_selected_url_launcher_implementation: ^1.0.0
|
|
''', logger: BufferLogger.test())!;
|
|
|
|
flutterProject.manifest = manifest;
|
|
flutterProject.isModule = true;
|
|
final Directory destination = flutterProject.directory.childDirectory('lib');
|
|
await injectBuildTimePluginFilesForWebPlatform(
|
|
flutterProject,
|
|
destination: destination,
|
|
);
|
|
|
|
final File registrant = flutterProject.directory
|
|
.childDirectory('lib')
|
|
.childFile('web_plugin_registrant.dart');
|
|
|
|
expect(registrant, exists);
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
contains(
|
|
"import 'package:user_selected_url_launcher_implementation/src/web_plugin.dart';",
|
|
),
|
|
);
|
|
expect(
|
|
registrant.readAsStringSync(),
|
|
isNot(contains("import 'package:url_launcher/src/web_plugin.dart';")),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
});
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Android registrant, but does not include Dart-only plugins',
|
|
() async {
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
android:
|
|
dartPluginClass: SomePlugin
|
|
''');
|
|
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
|
|
final File registrantFile = androidProject.pluginRegistrantHost
|
|
.childDirectory(fs.path.join('src', 'main', 'java', 'io', 'flutter', 'plugins'))
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
|
|
expect(registrantFile, exists);
|
|
expect(registrantFile.readAsStringSync(), isNot(contains('SomePlugin')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated iOS registrant, but does not include Dart-only plugins',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
ios:
|
|
dartPluginClass: SomePlugin
|
|
''');
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
iosPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
);
|
|
|
|
final File registrantFile = iosProject.pluginRegistrantImplementation;
|
|
|
|
expect(registrantFile, exists);
|
|
expect(registrantFile.readAsStringSync(), isNot(contains('SomePlugin')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting does not overwrite unchanged registrant files',
|
|
() async {
|
|
createFakePlugin(fs);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File registrantHeader = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.h',
|
|
);
|
|
final DateTime headerLastModified = registrantHeader.lastModifiedSync();
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
// Check that the last modified date is the same.
|
|
expect(registrantHeader.lastModifiedSync(), headerLastModified);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated macos registrant, but does not include Dart-only plugins',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
macos:
|
|
dartPluginClass: SomePlugin
|
|
''');
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
macOSPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
);
|
|
|
|
final File registrantFile = macosProject.managedDirectory.childFile(
|
|
'GeneratedPluginRegistrant.swift',
|
|
);
|
|
|
|
expect(registrantFile, exists);
|
|
expect(registrantFile.readAsStringSync(), isNot(contains('SomePlugin')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Invalid yaml does not crash plugin lookup.',
|
|
() async {
|
|
flutterProject.isModule = true;
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync(r'''
|
|
"aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\"
|
|
''');
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
macOSPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
);
|
|
|
|
final File registrantFile = macosProject.managedDirectory.childFile(
|
|
'GeneratedPluginRegistrant.swift',
|
|
);
|
|
|
|
expect(registrantFile, exists);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Linux registrant',
|
|
() async {
|
|
createFakePlugin(fs);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File registrantHeader = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.h',
|
|
);
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantHeader, exists);
|
|
expect(registrantImpl, exists);
|
|
expect(
|
|
registrantImpl.readAsStringSync(),
|
|
contains('some_plugin_register_with_registrar'),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'user-selected implementation overrides inline implementation',
|
|
() async {
|
|
final List<Directory> directories = createFakePlugins(fs, <String>[
|
|
'user_selected_url_launcher_implementation',
|
|
'url_launcher',
|
|
]);
|
|
|
|
// Add inline native implementation to `user_selected_url_launcher_implementation`
|
|
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
implements: url_launcher
|
|
platforms:
|
|
linux:
|
|
pluginClass: UserSelectedUrlLauncherLinux
|
|
''');
|
|
|
|
// Add inline native implementation to `url_launcher`
|
|
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
linux:
|
|
pluginClass: InlineUrlLauncherLinux
|
|
''');
|
|
|
|
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
|
name: my_app
|
|
version: 1.0.0
|
|
|
|
dependencies:
|
|
url_launcher: ^1.0.0
|
|
user_selected_url_launcher_implementation: ^1.0.0
|
|
''', logger: BufferLogger.test())!;
|
|
|
|
flutterProject.manifest = manifest;
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantImpl, exists);
|
|
expect(
|
|
registrantImpl.readAsStringSync(),
|
|
contains('user_selected_url_launcher_linux_register_with_registrar'),
|
|
);
|
|
expect(
|
|
registrantImpl.readAsStringSync(),
|
|
isNot(contains('inline_url_launcher_linux_register_with_registrar')),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'user-selected implementation overrides default implementation',
|
|
() async {
|
|
final List<Directory> directories = createFakePlugins(fs, <String>[
|
|
'user_selected_url_launcher_implementation',
|
|
'url_launcher',
|
|
'url_launcher_linux',
|
|
]);
|
|
|
|
// Add inline native implementation to `user_selected_url_launcher_implementation`
|
|
directories[0].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
implements: url_launcher
|
|
platforms:
|
|
linux:
|
|
pluginClass: UserSelectedUrlLauncherLinux
|
|
''');
|
|
|
|
// Add default native implementation to `url_launcher`
|
|
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
linux:
|
|
default_package: url_launcher_linux
|
|
''');
|
|
|
|
// Add inline native implementation to `url_launcher_linux`
|
|
directories[1].childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
linux:
|
|
pluginClass: InlineUrlLauncherLinux
|
|
''');
|
|
|
|
final FlutterManifest manifest = FlutterManifest.createFromString('''
|
|
name: my_app
|
|
version: 1.0.0
|
|
|
|
dependencies:
|
|
url_launcher: ^1.0.0
|
|
user_selected_url_launcher_implementation: ^1.0.0
|
|
''', logger: BufferLogger.test())!;
|
|
|
|
flutterProject.manifest = manifest;
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantImpl, exists);
|
|
expect(
|
|
registrantImpl.readAsStringSync(),
|
|
contains('user_selected_url_launcher_linux_register_with_registrar'),
|
|
);
|
|
expect(
|
|
registrantImpl.readAsStringSync(),
|
|
isNot(contains('inline_url_launcher_linux_register_with_registrar')),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Linux registrant, but does not include Dart-only plugins',
|
|
() async {
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
linux:
|
|
dartPluginClass: SomePlugin
|
|
''');
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File registrantImpl = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantImpl, exists);
|
|
|
|
final String contents = registrantImpl.readAsStringSync();
|
|
expect(contents, isNot(contains('SomePlugin')));
|
|
expect(contents, isNot(contains('some_plugin')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Linux plugin Cmake file',
|
|
() async {
|
|
createFakePlugin(fs);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File pluginMakefile = linuxProject.generatedPluginCmakeFile;
|
|
|
|
expect(pluginMakefile, exists);
|
|
final String contents = pluginMakefile.readAsStringSync();
|
|
expect(contents, contains('some_plugin'));
|
|
expect(
|
|
contents,
|
|
contains(r'target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)'),
|
|
);
|
|
expect(
|
|
contents,
|
|
contains(r'list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)'),
|
|
);
|
|
expect(
|
|
contents,
|
|
contains(r'list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})'),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Generated Linux plugin files sorts by plugin name',
|
|
() async {
|
|
createFakePlugins(fs, <String>[
|
|
'plugin_d',
|
|
'plugin_a',
|
|
'/local_plugins/plugin_c',
|
|
'/local_plugins/plugin_b',
|
|
]);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, linuxPlatform: true);
|
|
|
|
final File pluginCmakeFile = linuxProject.generatedPluginCmakeFile;
|
|
final File pluginRegistrant = linuxProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
for (final file in <File>[pluginCmakeFile, pluginRegistrant]) {
|
|
final String contents = file.readAsStringSync();
|
|
expect(contents.indexOf('plugin_a'), lessThan(contents.indexOf('plugin_b')));
|
|
expect(contents.indexOf('plugin_b'), lessThan(contents.indexOf('plugin_c')));
|
|
expect(contents.indexOf('plugin_c'), lessThan(contents.indexOf('plugin_d')));
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Windows registrant',
|
|
() async {
|
|
createFakePlugin(fs);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, windowsPlatform: true);
|
|
|
|
final File registrantHeader = windowsProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.h',
|
|
);
|
|
final File registrantImpl = windowsProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantHeader, exists);
|
|
expect(registrantImpl, exists);
|
|
expect(registrantImpl.readAsStringSync(), contains('SomePluginRegisterWithRegistrar'));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Injecting creates generated Windows registrant, but does not include Dart-only plugins',
|
|
() async {
|
|
// Create a plugin without a pluginClass.
|
|
final Directory pluginDirectory = createFakePlugin(fs);
|
|
pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
windows:
|
|
dartPluginClass: SomePlugin
|
|
''');
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, windowsPlatform: true);
|
|
|
|
final File registrantImpl = windowsProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
|
|
expect(registrantImpl, exists);
|
|
expect(registrantImpl.readAsStringSync(), isNot(contains('SomePlugin')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Generated Windows plugin files sorts by plugin name',
|
|
() async {
|
|
createFakePlugins(fs, <String>[
|
|
'plugin_d',
|
|
'plugin_a',
|
|
'/local_plugins/plugin_c',
|
|
'/local_plugins/plugin_b',
|
|
]);
|
|
|
|
await injectPlugins(flutterProject, releaseMode: false, windowsPlatform: true);
|
|
|
|
final File pluginCmakeFile = windowsProject.generatedPluginCmakeFile;
|
|
final File pluginRegistrant = windowsProject.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
for (final file in <File>[pluginCmakeFile, pluginRegistrant]) {
|
|
final String contents = file.readAsStringSync();
|
|
expect(contents.indexOf('plugin_a'), lessThan(contents.indexOf('plugin_b')));
|
|
expect(contents.indexOf('plugin_b'), lessThan(contents.indexOf('plugin_c')));
|
|
expect(contents.indexOf('plugin_c'), lessThan(contents.indexOf('plugin_d')));
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Generated plugin CMake files always use posix-style paths',
|
|
() async {
|
|
// Re-run the setup using the Windows filesystem.
|
|
setUpProject(fsWindows);
|
|
createFakePlugin(fsWindows);
|
|
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
linuxPlatform: true,
|
|
windowsPlatform: true,
|
|
);
|
|
|
|
for (final project in <CmakeBasedProject?>[linuxProject, windowsProject]) {
|
|
final File pluginCmakefile = project!.generatedPluginCmakeFile;
|
|
|
|
expect(pluginCmakefile, exists);
|
|
final String contents = pluginCmakefile.readAsStringSync();
|
|
expect(contents, contains('add_subdirectory(flutter/ephemeral/.plugin_symlinks'));
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fsWindows,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'iOS and macOS project setup up Darwin Dependency Management',
|
|
() async {
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
iosPlatform: true,
|
|
macOSPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
);
|
|
expect(dependencyManagement.setupPlatforms, <FlutterDarwinPlatform>[
|
|
FlutterDarwinPlatform.ios,
|
|
FlutterDarwinPlatform.macos,
|
|
]);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'non-iOS or macOS project does not setup up Darwin Dependency Management',
|
|
() async {
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
await injectPlugins(
|
|
flutterProject,
|
|
releaseMode: false,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
);
|
|
expect(dependencyManagement.setupPlatforms, <FlutterDarwinPlatform>[]);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
});
|
|
|
|
group('createPluginSymlinks', () {
|
|
FeatureFlags? featureFlags;
|
|
|
|
setUp(() {
|
|
featureFlags = TestFeatureFlags(isLinuxEnabled: true, isWindowsEnabled: true);
|
|
});
|
|
|
|
testUsingContext(
|
|
'Symlinks are created for Linux plugins',
|
|
() async {
|
|
linuxProject.exists = true;
|
|
createFakePlugin(fs);
|
|
// refreshPluginsList should call createPluginSymlinks.
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(linuxProject.pluginSymlinkDirectory.childLink('some_plugin'), exists);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Symlinks are created for Windows plugins',
|
|
() async {
|
|
windowsProject.exists = true;
|
|
createFakePlugin(fs);
|
|
// refreshPluginsList should call createPluginSymlinks.
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
expect(windowsProject.pluginSymlinkDirectory.childLink('some_plugin'), exists);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Existing symlinks are removed when no longer in use with force',
|
|
() {
|
|
linuxProject.exists = true;
|
|
windowsProject.exists = true;
|
|
|
|
final dummyFiles = <File>[
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
];
|
|
for (final file in dummyFiles) {
|
|
file.createSync(recursive: true);
|
|
}
|
|
|
|
createPluginSymlinks(flutterProject, force: true);
|
|
|
|
for (final file in dummyFiles) {
|
|
expect(file, isNot(exists));
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Existing symlinks are removed automatically on refresh when no longer in use',
|
|
() async {
|
|
linuxProject.exists = true;
|
|
windowsProject.exists = true;
|
|
|
|
final dummyFiles = <File>[
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
];
|
|
for (final file in dummyFiles) {
|
|
file.createSync(recursive: true);
|
|
}
|
|
|
|
// refreshPluginsList should remove existing links and recreate on changes.
|
|
createFakePlugin(fs);
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
for (final file in dummyFiles) {
|
|
expect(file, isNot(exists));
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'createPluginSymlinks is a no-op without force when up to date',
|
|
() {
|
|
linuxProject.exists = true;
|
|
windowsProject.exists = true;
|
|
|
|
final dummyFiles = <File>[
|
|
flutterProject.linux.pluginSymlinkDirectory.childFile('dummy'),
|
|
flutterProject.windows.pluginSymlinkDirectory.childFile('dummy'),
|
|
];
|
|
for (final file in dummyFiles) {
|
|
file.createSync(recursive: true);
|
|
}
|
|
|
|
// Without force, this should do nothing to existing files.
|
|
createPluginSymlinks(flutterProject);
|
|
|
|
for (final file in dummyFiles) {
|
|
expect(file, exists);
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'createPluginSymlinks repairs missing links',
|
|
() async {
|
|
linuxProject.exists = true;
|
|
windowsProject.exists = true;
|
|
createFakePlugin(fs);
|
|
await refreshPluginsList(flutterProject);
|
|
|
|
final links = <Link>[
|
|
linuxProject.pluginSymlinkDirectory.childLink('some_plugin'),
|
|
windowsProject.pluginSymlinkDirectory.childLink('some_plugin'),
|
|
];
|
|
for (final link in links) {
|
|
link.deleteSync();
|
|
}
|
|
createPluginSymlinks(flutterProject);
|
|
|
|
for (final link in links) {
|
|
expect(link, exists);
|
|
}
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => featureFlags,
|
|
},
|
|
);
|
|
});
|
|
|
|
group('pubspec', () {
|
|
late Directory projectDir;
|
|
late Directory tempDir;
|
|
|
|
setUp(() {
|
|
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_plugin_test.');
|
|
projectDir = tempDir.childDirectory('flutter_project');
|
|
});
|
|
|
|
tearDown(() {
|
|
tryToDelete(tempDir);
|
|
});
|
|
|
|
void createPubspecFile(String yamlString) {
|
|
projectDir.childFile('pubspec.yaml')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync(yamlString);
|
|
}
|
|
|
|
testUsingContext('validatePubspecForPlugin works', () async {
|
|
const pluginYaml = '''
|
|
flutter:
|
|
plugin:
|
|
platforms:
|
|
ios:
|
|
pluginClass: SomePlugin
|
|
macos:
|
|
pluginClass: SomePlugin
|
|
windows:
|
|
pluginClass: SomePlugin
|
|
linux:
|
|
pluginClass: SomePlugin
|
|
web:
|
|
pluginClass: SomePlugin
|
|
fileName: lib/SomeFile.dart
|
|
android:
|
|
pluginClass: SomePlugin
|
|
package: AndroidPackage
|
|
''';
|
|
createPubspecFile(pluginYaml);
|
|
validatePubspecForPlugin(
|
|
projectDir: projectDir.absolute.path,
|
|
pluginClass: 'SomePlugin',
|
|
expectedPlatforms: <String>['ios', 'macos', 'windows', 'linux', 'android', 'web'],
|
|
androidIdentifier: 'AndroidPackage',
|
|
webFileName: 'lib/SomeFile.dart',
|
|
);
|
|
});
|
|
|
|
testUsingContext('createPlatformsYamlMap should create the correct map', () async {
|
|
final YamlMap map = Plugin.createPlatformsYamlMap(
|
|
<String>['ios', 'android', 'linux'],
|
|
'PluginClass',
|
|
'some.android.package',
|
|
);
|
|
expect(map['ios'], <String, String>{'pluginClass': 'PluginClass'});
|
|
expect(map['android'], <String, String>{
|
|
'pluginClass': 'PluginClass',
|
|
'package': 'some.android.package',
|
|
});
|
|
expect(map['linux'], <String, String>{'pluginClass': 'PluginClass'});
|
|
});
|
|
|
|
testUsingContext('createPlatformsYamlMap should create empty map', () async {
|
|
final YamlMap map = Plugin.createPlatformsYamlMap(<String>[], 'foo', 'bar');
|
|
expect(map.isEmpty, true);
|
|
});
|
|
});
|
|
|
|
group('Plugin files', () {
|
|
testWithoutContext('for SwiftPM and podspec paths for iOS and macOS plugins', () async {
|
|
final fs = MemoryFileSystem.test();
|
|
final plugin = Plugin(
|
|
name: 'test',
|
|
path: '/path/to/test/',
|
|
defaultPackagePlatforms: const <String, String>{},
|
|
pluginDartClassPlatforms: const <String, DartPluginClassAndFilePair>{},
|
|
platforms: const <String, PluginPlatform>{
|
|
IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: ''),
|
|
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test'),
|
|
},
|
|
dependencies: <String>[],
|
|
isDirectDependency: true,
|
|
isDevDependency: false,
|
|
);
|
|
expect(plugin.pluginSwiftPackagePath(fs, IOSPlugin.kConfigKey), '/path/to/test/ios/test');
|
|
expect(
|
|
plugin.pluginSwiftPackagePath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/macos/test',
|
|
);
|
|
expect(
|
|
plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey),
|
|
'/path/to/test/ios/test/Package.swift',
|
|
);
|
|
expect(
|
|
plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/macos/test/Package.swift',
|
|
);
|
|
expect(
|
|
plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey),
|
|
'/path/to/test/ios/test.podspec',
|
|
);
|
|
expect(
|
|
plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/macos/test.podspec',
|
|
);
|
|
});
|
|
|
|
testWithoutContext('for SwiftPM and podspec paths for darwin plugins', () async {
|
|
final fs = MemoryFileSystem.test();
|
|
final plugin = Plugin(
|
|
name: 'test',
|
|
path: '/path/to/test/',
|
|
defaultPackagePlatforms: const <String, String>{},
|
|
pluginDartClassPlatforms: const <String, DartPluginClassAndFilePair>{},
|
|
platforms: const <String, PluginPlatform>{
|
|
IOSPlugin.kConfigKey: IOSPlugin(
|
|
name: 'test',
|
|
classPrefix: '',
|
|
sharedDarwinSource: true,
|
|
),
|
|
MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test', sharedDarwinSource: true),
|
|
},
|
|
dependencies: <String>[],
|
|
isDirectDependency: true,
|
|
isDevDependency: false,
|
|
);
|
|
|
|
expect(
|
|
plugin.pluginSwiftPackagePath(fs, IOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test',
|
|
);
|
|
expect(
|
|
plugin.pluginSwiftPackagePath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test',
|
|
);
|
|
expect(
|
|
plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test/Package.swift',
|
|
);
|
|
expect(
|
|
plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test/Package.swift',
|
|
);
|
|
expect(
|
|
plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test.podspec',
|
|
);
|
|
expect(
|
|
plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey),
|
|
'/path/to/test/darwin/test.podspec',
|
|
);
|
|
});
|
|
|
|
testWithoutContext('for SwiftPM and podspec paths for non darwin plugins', () async {
|
|
final fs = MemoryFileSystem.test();
|
|
final plugin = Plugin(
|
|
name: 'test',
|
|
path: '/path/to/test/',
|
|
defaultPackagePlatforms: const <String, String>{},
|
|
pluginDartClassPlatforms: const <String, DartPluginClassAndFilePair>{},
|
|
platforms: const <String, PluginPlatform>{
|
|
WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: ''),
|
|
},
|
|
dependencies: <String>[],
|
|
isDirectDependency: true,
|
|
isDevDependency: false,
|
|
);
|
|
|
|
expect(plugin.pluginSwiftPackagePath(fs, IOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginSwiftPackagePath(fs, MacOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginSwiftPackagePath(fs, WindowsPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginSwiftPackageManifestPath(fs, WindowsPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey), isNull);
|
|
expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull);
|
|
});
|
|
});
|
|
|
|
testWithoutContext(
|
|
'Symlink failures give developer mode instructions on recent versions of Windows',
|
|
() async {
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
final os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]');
|
|
|
|
const e = FileSystemException('', '', OSError('', 1314));
|
|
|
|
expect(
|
|
() => handleSymlinkException(
|
|
e,
|
|
platform: platform,
|
|
os: os,
|
|
source: pubCachePath,
|
|
destination: ephemeralPackagePath,
|
|
),
|
|
throwsToolExit(message: 'start ms-settings:developers'),
|
|
);
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Symlink ERROR_ACCESS_DENIED failures show developers paths that were used',
|
|
() async {
|
|
final flutterProject = FakeFlutterProject()
|
|
..directory = globals.fs.currentDirectory.childDirectory('app');
|
|
final Directory windowsManagedDirectory = flutterProject.directory
|
|
.childDirectory('windows')
|
|
.childDirectory('flutter');
|
|
final windowsProject = FakeWindowsProject()
|
|
..managedDirectory = windowsManagedDirectory
|
|
..pluginSymlinkDirectory = windowsManagedDirectory
|
|
.childDirectory('ephemeral')
|
|
.childDirectory('.plugin_symlinks')
|
|
..exists = true;
|
|
|
|
final File dependenciesFile = flutterProject.directory.childFile(
|
|
'.flutter-plugins-dependencies',
|
|
);
|
|
flutterProject
|
|
..flutterPluginsDependenciesFile = dependenciesFile
|
|
..windows = windowsProject;
|
|
|
|
writePackageConfigFiles(directory: flutterProject.directory, mainLibName: 'my_app');
|
|
|
|
const dependenciesFileContents = r'''
|
|
{
|
|
"plugins": {
|
|
"windows": [
|
|
{
|
|
"name": "some_plugin",
|
|
"path": "C:\\some_plugin"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
''';
|
|
dependenciesFile.writeAsStringSync(dependenciesFileContents);
|
|
|
|
const expectedMessage =
|
|
'ERROR_ACCESS_DENIED file system exception thrown while trying to '
|
|
r'create a symlink from C:\some_plugin to '
|
|
r'C:\app\windows\flutter\ephemeral\.plugin_symlinks\some_plugin';
|
|
|
|
expect(
|
|
() => createPluginSymlinks(
|
|
flutterProject,
|
|
featureFlagsOverride: TestFeatureFlags(isWindowsEnabled: true),
|
|
),
|
|
throwsToolExit(message: expectedMessage),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () {
|
|
final handle = FileExceptionHandler();
|
|
final fileSystem = ErrorHandlingFileSystem(
|
|
platform: FakePlatform(),
|
|
delegate: MemoryFileSystem.test(
|
|
style: FileSystemStyle.windows,
|
|
opHandle: handle.opHandle,
|
|
),
|
|
);
|
|
const pluginSymlinkPath =
|
|
r'C:\app\windows\flutter\ephemeral\.plugin_symlinks\some_plugin';
|
|
handle.addError(
|
|
fileSystem.link(pluginSymlinkPath),
|
|
FileSystemOp.create,
|
|
const FileSystemException('', '', OSError('', 5)),
|
|
);
|
|
return fileSystem;
|
|
},
|
|
Platform: () => FakePlatform(operatingSystem: 'windows'),
|
|
ProcessManager: () => FakeProcessManager.empty(),
|
|
},
|
|
);
|
|
|
|
testWithoutContext(
|
|
'Symlink failures instruct developers to run as administrator on older versions of Windows',
|
|
() async {
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
final os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14393]');
|
|
|
|
const e = FileSystemException('', '', OSError('', 1314));
|
|
|
|
expect(
|
|
() => handleSymlinkException(
|
|
e,
|
|
platform: platform,
|
|
os: os,
|
|
source: pubCachePath,
|
|
destination: ephemeralPackagePath,
|
|
),
|
|
throwsToolExit(message: 'administrator'),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWithoutContext(
|
|
'Symlink failures instruct developers to have their project on the same drive as their SDK',
|
|
() async {
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
final os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972]');
|
|
|
|
const e = FileSystemException('', '', OSError('', 1));
|
|
|
|
expect(
|
|
() => handleSymlinkException(
|
|
e,
|
|
platform: platform,
|
|
os: os,
|
|
source: pubCachePath,
|
|
destination: ephemeralPackagePath,
|
|
),
|
|
throwsToolExit(
|
|
message: 'Try moving your Flutter project to the same drive as your Flutter SDK',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('Symlink failures only give instructions for specific errors', () async {
|
|
final Platform platform = FakePlatform(operatingSystem: 'windows');
|
|
final os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14393]');
|
|
|
|
const e = FileSystemException('', '', OSError('', 999));
|
|
|
|
expect(
|
|
() => handleSymlinkException(
|
|
e,
|
|
platform: platform,
|
|
os: os,
|
|
source: pubCachePath,
|
|
destination: ephemeralPackagePath,
|
|
),
|
|
returnsNormally,
|
|
);
|
|
});
|
|
|
|
group('injectPlugins in release mode', () {
|
|
const testPluginName = 'test_plugin';
|
|
|
|
// Fake pub to override dev dependencies of flutterProject.
|
|
|
|
testUsingContext(
|
|
'excludes dev dependencies from Android plugin registrant',
|
|
() async {
|
|
final Directory pluginDir = createPlugin(
|
|
name: testPluginName,
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'),
|
|
},
|
|
isDevDependency: true,
|
|
);
|
|
|
|
// injectPlugins will fail if main native class not found in expected spot, so add
|
|
// it first.
|
|
pluginDir
|
|
.childDirectory('android')
|
|
.childDirectory('src')
|
|
.childDirectory('main')
|
|
.childDirectory('java')
|
|
.childDirectory('bar')
|
|
.childDirectory('foo')
|
|
.childFile('Foo.java')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
|
|
|
|
// Test non-release mode.
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: false);
|
|
final File generatedPluginRegistrant =
|
|
flutterProject.android.generatedPluginRegistrantFile;
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(generatedPluginRegistrant.readAsStringSync(), contains('bar.foo.Foo'));
|
|
|
|
// Test release mode.
|
|
await injectPlugins(flutterProject, androidPlatform: true, releaseMode: true);
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(generatedPluginRegistrant.readAsStringSync(), isNot(contains('bar.foo.Foo')));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: () => const ThrowingPub(),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'includes dev dependencies from iOS plugin registrant',
|
|
() async {
|
|
createPlugin(
|
|
name: testPluginName,
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'ios': _PluginPlatformInfo(pluginClass: 'Foo'),
|
|
},
|
|
isDevDependency: true,
|
|
);
|
|
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
const devDepImport = '#import <$testPluginName/Foo.h>';
|
|
|
|
// Test non-release mode.
|
|
await injectPlugins(
|
|
flutterProject,
|
|
iosPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
releaseMode: false,
|
|
);
|
|
final File generatedPluginRegistrantImpl =
|
|
flutterProject.ios.pluginRegistrantImplementation;
|
|
expect(generatedPluginRegistrantImpl, exists);
|
|
expect(generatedPluginRegistrantImpl.readAsStringSync(), contains(devDepImport));
|
|
|
|
// Test release mode.
|
|
await injectPlugins(
|
|
flutterProject,
|
|
iosPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
releaseMode: true,
|
|
);
|
|
expect(generatedPluginRegistrantImpl, exists);
|
|
expect(generatedPluginRegistrantImpl.readAsStringSync(), contains(devDepImport));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: () => const ThrowingPub(),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'excludes dev dependencies from Linux plugin registrant',
|
|
() async {
|
|
createPlugin(
|
|
name: testPluginName,
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'linux': _PluginPlatformInfo(pluginClass: 'Foo'),
|
|
},
|
|
isDevDependency: true,
|
|
);
|
|
|
|
const expectedDevDepImport = '#include <$testPluginName/foo.h>';
|
|
|
|
// Test non-release mode.
|
|
await injectPlugins(flutterProject, linuxPlatform: true, releaseMode: false);
|
|
final File generatedPluginRegistrant = flutterProject.linux.managedDirectory.childFile(
|
|
'generated_plugin_registrant.cc',
|
|
);
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(generatedPluginRegistrant.readAsStringSync(), contains(expectedDevDepImport));
|
|
|
|
// Test release mode.
|
|
await injectPlugins(flutterProject, linuxPlatform: true, releaseMode: true);
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(
|
|
generatedPluginRegistrant.readAsStringSync(),
|
|
isNot(contains(expectedDevDepImport)),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: () => const ThrowingPub(),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'includes dev dependencies from MacOS plugin registrant',
|
|
() async {
|
|
createPlugin(
|
|
name: testPluginName,
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'macos': _PluginPlatformInfo(pluginClass: 'Foo'),
|
|
},
|
|
isDevDependency: true,
|
|
);
|
|
final dependencyManagement = FakeDarwinDependencyManagement();
|
|
const expectedDevDepRegistration = 'Foo.register';
|
|
|
|
// Test non-release mode.
|
|
await injectPlugins(
|
|
flutterProject,
|
|
macOSPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
releaseMode: false,
|
|
);
|
|
final File generatedPluginRegistrant = flutterProject.macos.managedDirectory.childFile(
|
|
'GeneratedPluginRegistrant.swift',
|
|
);
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(
|
|
generatedPluginRegistrant.readAsStringSync(),
|
|
contains(expectedDevDepRegistration),
|
|
);
|
|
|
|
// Test release mode.
|
|
await injectPlugins(
|
|
flutterProject,
|
|
macOSPlatform: true,
|
|
darwinDependencyManagement: dependencyManagement,
|
|
releaseMode: true,
|
|
);
|
|
expect(generatedPluginRegistrant, exists);
|
|
expect(
|
|
generatedPluginRegistrant.readAsStringSync(),
|
|
contains(expectedDevDepRegistration),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: () => const ThrowingPub(),
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'excludes dev dependencies from Windows plugin registrant',
|
|
() async {
|
|
final Directory pluginDir = createPlugin(
|
|
name: testPluginName,
|
|
platforms: const <String, _PluginPlatformInfo>{
|
|
'windows': _PluginPlatformInfo(pluginClass: 'Foo'),
|
|
},
|
|
isDevDependency: true,
|
|
);
|
|
|
|
const expectedDevDepRegistration = '#include <$testPluginName/foo.h>';
|
|
writePackageConfigFiles(
|
|
directory: flutterProject.directory,
|
|
mainLibName: 'my_app',
|
|
packages: <String, String>{testPluginName: pluginDir.path},
|
|
devDependencies: <String>[testPluginName],
|
|
);
|
|
|
|
// Test non-release mode.
|
|
await injectPlugins(flutterProject, windowsPlatform: true, releaseMode: false);
|
|
final File generatedPluginRegistrantImpl = flutterProject.windows.managedDirectory
|
|
.childFile('generated_plugin_registrant.cc');
|
|
expect(generatedPluginRegistrantImpl, exists);
|
|
expect(
|
|
generatedPluginRegistrantImpl.readAsStringSync(),
|
|
contains(expectedDevDepRegistration),
|
|
);
|
|
|
|
// Test release mode.
|
|
await injectPlugins(flutterProject, windowsPlatform: true, releaseMode: true);
|
|
expect(generatedPluginRegistrantImpl, exists);
|
|
expect(
|
|
generatedPluginRegistrantImpl.readAsStringSync(),
|
|
isNot(contains(expectedDevDepRegistration)),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fs,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Pub: () => const ThrowingPub(),
|
|
},
|
|
);
|
|
});
|
|
|
|
group('flutterPluginsListHasDevDependencies', () {
|
|
testWithoutContext('throws if file does not exist', () {
|
|
final fileSystem = MemoryFileSystem.test();
|
|
final File pluginsFile = fileSystem.file('.flutter-plugins-dependencies');
|
|
|
|
expect(
|
|
() => flutterPluginsListHasDevDependencies(pluginsFile),
|
|
throwsA(isA<FileSystemException>()),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('throws if file is malformed', () {
|
|
final fileSystem = MemoryFileSystem.test();
|
|
final File pluginsFile = fileSystem.file('.flutter-plugins-dependencies');
|
|
|
|
pluginsFile.writeAsStringSync('This is not JSON');
|
|
|
|
expect(
|
|
() => flutterPluginsListHasDevDependencies(pluginsFile),
|
|
throwsA(isA<FormatException>()),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('Returns false if has no dependencies', () {
|
|
final fileSystem = MemoryFileSystem.test();
|
|
final File pluginsFile = fileSystem.file('.flutter-plugins-dependencies');
|
|
|
|
pluginsFile.writeAsStringSync('''
|
|
{
|
|
"plugins": {}
|
|
}
|
|
''');
|
|
expect(flutterPluginsListHasDevDependencies(pluginsFile), isFalse);
|
|
});
|
|
|
|
testWithoutContext('Returns false if has no dev dependencies', () {
|
|
final fileSystem = MemoryFileSystem.test();
|
|
final File pluginsFile = fileSystem.file('.flutter-plugins-dependencies');
|
|
|
|
pluginsFile.writeAsStringSync('''
|
|
{
|
|
"plugins": {
|
|
"ios": [
|
|
{
|
|
"name": "foo_package",
|
|
"dev_dependency": false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
''');
|
|
|
|
expect(flutterPluginsListHasDevDependencies(pluginsFile), isFalse);
|
|
});
|
|
|
|
testWithoutContext('Returns true if has dev dependencies', () {
|
|
final fileSystem = MemoryFileSystem.test();
|
|
final File pluginsFile = fileSystem.file('.flutter-plugins-dependencies');
|
|
|
|
pluginsFile.writeAsStringSync('''
|
|
{
|
|
"plugins": {
|
|
"ios": [
|
|
{
|
|
"name": "foo_package",
|
|
"dev_dependency": true
|
|
}
|
|
]
|
|
}
|
|
}
|
|
''');
|
|
|
|
expect(flutterPluginsListHasDevDependencies(pluginsFile), isTrue);
|
|
});
|
|
});
|
|
});
|
|
|
|
testUsingContext(
|
|
'exits tool when deleting .plugin_symlinks fails',
|
|
() async {
|
|
final flutterProject = FakeFlutterProject()
|
|
..directory = globals.fs.currentDirectory.childDirectory('app');
|
|
final flutterManifest = FakeFlutterManifest();
|
|
final Directory windowsManagedDirectory = flutterProject.directory
|
|
.childDirectory('windows')
|
|
.childDirectory('flutter');
|
|
final windowsProject = FakeWindowsProject()
|
|
..managedDirectory = windowsManagedDirectory
|
|
..cmakeFile = windowsManagedDirectory.parent.childFile('CMakeLists.txt')
|
|
..generatedPluginCmakeFile = windowsManagedDirectory.childFile('generated_plugins.mk')
|
|
..pluginSymlinkDirectory = windowsManagedDirectory
|
|
.childDirectory('ephemeral')
|
|
.childDirectory('.plugin_symlinks')
|
|
..exists = true;
|
|
|
|
flutterProject
|
|
..manifest = flutterManifest
|
|
..flutterPluginsDependenciesFile = flutterProject.directory.childFile(
|
|
'.flutter-plugins-dependencies',
|
|
)
|
|
..windows = windowsProject;
|
|
|
|
writePackageConfigFiles(directory: flutterProject.directory, mainLibName: 'my_app');
|
|
|
|
createPluginSymlinks(
|
|
flutterProject,
|
|
force: true,
|
|
featureFlagsOverride: TestFeatureFlags(isWindowsEnabled: true),
|
|
);
|
|
|
|
expect(
|
|
() => createPluginSymlinks(
|
|
flutterProject,
|
|
force: true,
|
|
featureFlagsOverride: TestFeatureFlags(isWindowsEnabled: true),
|
|
),
|
|
throwsToolExit(
|
|
message: RegExp(
|
|
'Unable to delete file or directory at '
|
|
r'"C:\\app\\windows\\flutter\\ephemeral\\\.plugin_symlinks"',
|
|
),
|
|
),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () {
|
|
final handle = FileExceptionHandler();
|
|
final fileSystem = ErrorHandlingFileSystem(
|
|
platform: FakePlatform(),
|
|
delegate: MemoryFileSystem.test(
|
|
style: FileSystemStyle.windows,
|
|
opHandle: handle.opHandle,
|
|
),
|
|
);
|
|
const symlinkDirectoryPath = r'C:\app\windows\flutter\ephemeral\.plugin_symlinks';
|
|
handle.addError(
|
|
fileSystem.directory(symlinkDirectoryPath),
|
|
FileSystemOp.delete,
|
|
const PathNotFoundException(
|
|
symlinkDirectoryPath,
|
|
OSError('The system cannot find the path specified.', 3),
|
|
),
|
|
);
|
|
return fileSystem;
|
|
},
|
|
ProcessManager: () => FakeProcessManager.empty(),
|
|
},
|
|
);
|
|
}
|
|
|
|
class FakeFlutterManifest extends Fake implements FlutterManifest {
|
|
@override
|
|
late Set<String> dependencies = <String>{};
|
|
@override
|
|
late String appName;
|
|
@override
|
|
YamlMap toYaml() => YamlMap.wrap(<String, String>{});
|
|
}
|
|
|
|
class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter {
|
|
@override
|
|
bool get isInstalled => false;
|
|
}
|
|
|
|
class FakeFlutterProject extends Fake implements FlutterProject {
|
|
@override
|
|
bool isModule = false;
|
|
|
|
@override
|
|
late FlutterManifest manifest;
|
|
|
|
@override
|
|
late Directory directory;
|
|
|
|
@override
|
|
late File flutterPluginsDependenciesFile;
|
|
|
|
@override
|
|
late IosProject ios;
|
|
|
|
@override
|
|
late AndroidProject android;
|
|
|
|
@override
|
|
late WebProject web;
|
|
|
|
@override
|
|
late MacOSProject macos;
|
|
|
|
@override
|
|
late LinuxProject linux;
|
|
|
|
@override
|
|
late WindowsProject windows;
|
|
|
|
@override
|
|
File get packageConfig => directory.childDirectory('.dart_tool').childFile('package_config.json');
|
|
}
|
|
|
|
class FakeMacOSProject extends Fake implements MacOSProject {
|
|
@override
|
|
String pluginConfigKey = 'macos';
|
|
|
|
bool exists = false;
|
|
|
|
@override
|
|
late File podfile;
|
|
|
|
@override
|
|
late File podManifestLock;
|
|
|
|
@override
|
|
bool usesSwiftPackageManager = false;
|
|
|
|
@override
|
|
late Directory managedDirectory;
|
|
|
|
@override
|
|
bool existsSync() => exists;
|
|
}
|
|
|
|
class FakeIosProject extends Fake implements IosProject {
|
|
@override
|
|
String pluginConfigKey = 'ios';
|
|
|
|
bool testExists = false;
|
|
|
|
@override
|
|
bool existsSync() => testExists;
|
|
|
|
@override
|
|
bool get exists => testExists;
|
|
|
|
@override
|
|
late Directory pluginRegistrantHost;
|
|
|
|
@override
|
|
File get pluginRegistrantHeader => pluginRegistrantHost.childFile('GeneratedPluginRegistrant.h');
|
|
|
|
@override
|
|
File get pluginRegistrantImplementation =>
|
|
pluginRegistrantHost.childFile('GeneratedPluginRegistrant.m');
|
|
|
|
@override
|
|
late File podfile;
|
|
|
|
@override
|
|
late File podManifestLock;
|
|
|
|
@override
|
|
bool usesSwiftPackageManager = false;
|
|
}
|
|
|
|
class FakeAndroidProject extends Fake implements AndroidProject {
|
|
@override
|
|
String pluginConfigKey = 'android';
|
|
|
|
bool exists = false;
|
|
|
|
@override
|
|
late Directory pluginRegistrantHost;
|
|
|
|
@override
|
|
late Directory hostAppGradleRoot;
|
|
|
|
@override
|
|
late File appManifestFile;
|
|
|
|
late AndroidEmbeddingVersion embeddingVersion;
|
|
|
|
@override
|
|
bool existsSync() => exists;
|
|
|
|
@override
|
|
AndroidEmbeddingVersion getEmbeddingVersion() {
|
|
return embeddingVersion;
|
|
}
|
|
|
|
@override
|
|
AndroidEmbeddingVersionResult computeEmbeddingVersion() {
|
|
return AndroidEmbeddingVersionResult(embeddingVersion, 'reasons for version');
|
|
}
|
|
|
|
@override
|
|
File get generatedPluginRegistrantFile => hostAppGradleRoot
|
|
.childDirectory('app')
|
|
.childDirectory('src')
|
|
.childDirectory('main')
|
|
.childDirectory('java')
|
|
.childDirectory('io')
|
|
.childDirectory('flutter')
|
|
.childDirectory('plugins')
|
|
.childFile('GeneratedPluginRegistrant.java');
|
|
}
|
|
|
|
class FakeWebProject extends Fake implements WebProject {
|
|
@override
|
|
String pluginConfigKey = 'web';
|
|
|
|
@override
|
|
late Directory libDirectory;
|
|
|
|
bool exists = false;
|
|
|
|
@override
|
|
bool existsSync() => exists;
|
|
}
|
|
|
|
class FakeWindowsProject extends Fake implements WindowsProject {
|
|
@override
|
|
String pluginConfigKey = 'windows';
|
|
|
|
@override
|
|
late Directory managedDirectory;
|
|
|
|
@override
|
|
late Directory ephemeralDirectory;
|
|
|
|
@override
|
|
late Directory pluginSymlinkDirectory;
|
|
|
|
@override
|
|
late File cmakeFile;
|
|
|
|
@override
|
|
late File generatedPluginCmakeFile;
|
|
bool exists = false;
|
|
|
|
@override
|
|
bool existsSync() => exists;
|
|
}
|
|
|
|
class FakeLinuxProject extends Fake implements LinuxProject {
|
|
@override
|
|
String pluginConfigKey = 'linux';
|
|
|
|
@override
|
|
late Directory managedDirectory;
|
|
|
|
@override
|
|
late Directory ephemeralDirectory;
|
|
|
|
@override
|
|
late Directory pluginSymlinkDirectory;
|
|
|
|
@override
|
|
late File cmakeFile;
|
|
|
|
@override
|
|
late File generatedPluginCmakeFile;
|
|
bool exists = false;
|
|
|
|
@override
|
|
bool existsSync() => exists;
|
|
}
|
|
|
|
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
|
|
FakeOperatingSystemUtils(this.name);
|
|
|
|
@override
|
|
final String name;
|
|
}
|
|
|
|
class FakeSystemClock extends Fake implements SystemClock {
|
|
late DateTime currentTime;
|
|
|
|
@override
|
|
DateTime now() {
|
|
return currentTime;
|
|
}
|
|
}
|
|
|
|
class FakeDarwinDependencyManagement extends Fake implements DarwinDependencyManagement {
|
|
List<FlutterDarwinPlatform> setupPlatforms = <FlutterDarwinPlatform>[];
|
|
|
|
@override
|
|
Future<void> setUp({required FlutterDarwinPlatform platform}) async {
|
|
setupPlatforms.add(platform);
|
|
}
|
|
}
|
|
|
|
/// A fake of [CocoaPods] that writes calls to [invalidatePodInstallOutput] to [capturedInvocations].
|
|
final class FakeCocoaPodsCapturesInvalidate extends Fake implements CocoaPods {
|
|
final capturedInvocations = <XcodeBasedProject>[];
|
|
|
|
@override
|
|
void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {
|
|
capturedInvocations.add(xcodeProject);
|
|
}
|
|
}
|