Removes dev dependencies from generated plugin registrant for non-Android platforms (#161828)

Removes dev dependencies from the generated plugin registrants for all
platforms since they will be removed from release builds (this was
already done for Android, mostly in
https://github.com/flutter/flutter/pull/161343).

Fixes https://github.com/flutter/flutter/issues/161348.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

<!-- 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
This commit is contained in:
Camille Simon 2025-01-30 11:51:07 -06:00 committed by GitHub
parent 1dd3f5d45b
commit fffbf663ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 255 additions and 79 deletions

View File

@ -363,20 +363,11 @@ List<Map<String, Object?>> _extractPlatformMaps(Iterable<Plugin> plugins, String
];
}
Future<void> _writeAndroidPluginRegistrant(
FlutterProject project,
List<Plugin> plugins, {
required bool releaseMode,
}) async {
Iterable<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(
Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin> methodChannelPlugins = _filterMethodChannelPlugins(
plugins,
AndroidPlugin.kConfigKey,
);
// TODO(camsim99): Remove dev dependencies from release builds for all platforms. See https://github.com/flutter/flutter/issues/161348.
if (releaseMode) {
methodChannelPlugins = methodChannelPlugins.where((Plugin p) => !p.isDevDependency);
}
final List<Map<String, Object?>> androidPlugins = _extractPlatformMaps(
methodChannelPlugins,
AndroidPlugin.kConfigKey,
@ -1204,7 +1195,7 @@ Future<void> injectBuildTimePluginFilesForWebPlatform(
/// Injects plugins found in `pubspec.yaml` into the platform-specific projects.
///
/// The injected files are required by the flutter app as soon as possible, so
/// The injected files are required by the Flutter app as soon as possible, so
/// it can be built.
///
/// Files written by this method end up in platform-specific locations that are
@ -1225,18 +1216,19 @@ Future<void> injectPlugins(
DarwinDependencyManagement? darwinDependencyManagement,
bool? releaseMode,
}) async {
final List<Plugin> plugins = await findPlugins(project);
List<Plugin> plugins = await findPlugins(project);
if (releaseMode ?? false) {
plugins = plugins.where((Plugin p) => !p.isDevDependency).toList();
}
final Map<String, List<Plugin>> pluginsByPlatform = _resolvePluginImplementations(
plugins,
pluginResolutionType: _PluginResolutionType.nativeOrDart,
);
if (androidPlatform) {
await _writeAndroidPluginRegistrant(
project,
pluginsByPlatform[AndroidPlugin.kConfigKey]!,
releaseMode: releaseMode ?? false,
);
await _writeAndroidPluginRegistrant(project, pluginsByPlatform[AndroidPlugin.kConfigKey]!);
}
if (iosPlatform) {
await _writeIOSPluginRegistrant(project, pluginsByPlatform[IOSPlugin.kConfigKey]!);

View File

@ -244,7 +244,7 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin {
/// The [name] of the plugin is required. Additionally, either:
/// - [defaultPackage], or
/// - an implementation consisting of:
/// - the [pluginClass] (with optional [classPrefix]) that will be the entry
/// - the [classPrefix] (with optional [pluginClass]) that will be the entry
/// point to the plugin's native code, and/or
/// - the [dartPluginClass] with optional [dartFileName] that will be
/// the entry point for the plugin's Dart code

View File

@ -637,6 +637,18 @@ class AndroidProject extends FlutterProjectPlatform {
return hostAppGradleRoot.childFile('AndroidManifest.xml');
}
File get generatedPluginRegistrantFile {
return hostAppGradleRoot
.childDirectory('app')
.childDirectory('src')
.childDirectory('main')
.childDirectory('java')
.childDirectory('io')
.childDirectory('flutter')
.childDirectory('plugins')
.childFile('GeneratedPluginRegistrant.java');
}
File get gradleAppOutV1File => gradleAppOutV1Directory.childFile('app-debug.apk');
Directory get gradleAppOutV1Directory {

View File

@ -2510,6 +2510,226 @@ The Flutter Preview device does not support the following plugins from your pubs
returnsNormally,
);
});
group('injectPlugins in release mode', () {
const String testPluginName = 'test_plugin';
// Fake pub to override dev dependencies of flutterProject.
final Pub fakePubWithTestPluginDevDependency = FakePubWithPrimedDeps(
devDependencies: <String>{testPluginName},
);
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'),
},
);
// 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(),
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => fakePubWithTestPluginDevDependency,
},
);
testUsingContext(
'excludes dev dependencies from iOS plugin registrant',
() async {
createPlugin(
name: testPluginName,
platforms: const <String, _PluginPlatformInfo>{
'ios': _PluginPlatformInfo(pluginClass: 'Foo'),
},
);
final FakeDarwinDependencyManagement dependencyManagement =
FakeDarwinDependencyManagement();
const String 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(), isNot(contains(devDepImport)));
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => fakePubWithTestPluginDevDependency,
},
);
testUsingContext(
'excludes dev dependencies from Linux plugin registrant',
() async {
createPlugin(
name: testPluginName,
platforms: const <String, _PluginPlatformInfo>{
'linux': _PluginPlatformInfo(pluginClass: 'Foo'),
},
);
const String 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(),
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => fakePubWithTestPluginDevDependency,
},
);
testUsingContext(
'excludes dev dependencies from MacOS plugin registrant',
() async {
createPlugin(
name: testPluginName,
platforms: const <String, _PluginPlatformInfo>{
'macos': _PluginPlatformInfo(pluginClass: 'Foo'),
},
);
final FakeDarwinDependencyManagement dependencyManagement =
FakeDarwinDependencyManagement();
const String 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(),
isNot(contains(expectedDevDepRegistration)),
);
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => fakePubWithTestPluginDevDependency,
},
);
testUsingContext(
'excludes dev dependencies from Windows plugin registrant',
() async {
createPlugin(
name: testPluginName,
platforms: const <String, _PluginPlatformInfo>{
'windows': _PluginPlatformInfo(pluginClass: 'Foo'),
},
);
const String expectedDevDepRegistration = '#include <$testPluginName/foo.h>';
// 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(),
FeatureFlags: enableExplicitPackageDependencies,
Pub: () => fakePubWithTestPluginDevDependency,
},
);
});
});
testUsingContext(
@ -2718,6 +2938,17 @@ class FakeAndroidProject extends Fake implements AndroidProject {
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 {

View File

@ -1,58 +0,0 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/template.dart';
import 'package:flutter_tools/src/flutter_plugins.dart';
import 'package:flutter_tools/src/isolated/mustache_template.dart';
import 'package:flutter_tools/src/platform_plugins.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
import '../../src/common.dart';
const TemplateRenderer renderer = MustacheTemplateRenderer();
void main() {
testWithoutContext('Win32 injects Win32 plugins', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
setUpProject(fileSystem);
final FlutterProject flutterProject = FlutterProject.fromDirectoryTest(
fileSystem.currentDirectory,
);
await writeWindowsPluginFiles(flutterProject, <Plugin>[
Plugin(
name: 'test',
path: 'foo',
defaultPackagePlatforms: const <String, String>{},
pluginDartClassPlatforms: const <String, DartPluginClassAndFilePair>{},
platforms: const <String, PluginPlatform>{
WindowsPlugin.kConfigKey: WindowsPlugin(
name: 'test',
pluginClass: 'Foo',
variants: <PluginPlatformVariant>{PluginPlatformVariant.win32},
),
},
dependencies: <String>[],
isDirectDependency: true,
isDevDependency: false,
),
], renderer);
final Directory managed = flutterProject.windows.managedDirectory;
expect(flutterProject.windows.generatedPluginCmakeFile, exists);
expect(managed.childFile('generated_plugin_registrant.h'), exists);
expect(
managed.childFile('generated_plugin_registrant.cc').readAsStringSync(),
contains('#include <test/foo.h>'),
);
});
}
void setUpProject(FileSystem fileSystem) {
fileSystem.file('pubspec.yaml').createSync();
}

View File

@ -27,8 +27,7 @@ final class FakePubWithPrimedDeps implements Pub {
'name': rootPackageName,
'kind': 'root',
'dependencies': <String>[...dependencies.keys, ...devDependencies]..sort(),
'directDependencies': <String>[...?dependencies[rootPackageName], ...devDependencies]
..sort(),
'directDependencies': <String>[...?dependencies[rootPackageName]]..sort(),
'devDependencies': <String>[...devDependencies],
},
];