diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 8524e6ac22..ad5b9554d2 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -239,9 +239,8 @@ bool _writeFlutterPluginsList( final String? oldPluginsFileStringContent = _readFileContent(pluginsFile); bool pluginsChanged = true; if (oldPluginsFileStringContent != null) { - Object? decodedJson; try { - decodedJson = jsonDecode(oldPluginsFileStringContent); + final Object? decodedJson = jsonDecode(oldPluginsFileStringContent); if (decodedJson is Map) { final String jsonOfNewPluginsMap = jsonEncode(pluginsMap); final String jsonOfOldPluginsMap = jsonEncode(decodedJson[_kFlutterPluginsPluginListKey]); @@ -1095,6 +1094,11 @@ void _createPlatformPluginSymlinks( /// dependencies declared in `pubspec.yaml`. /// /// Assumes `pub get` has been executed since last change to `pubspec.yaml`. +/// +/// Unless explicitly specified, [determineDevDependencies] is disabled by +/// default; if set to `true`, plugins that are development-only dependencies +/// may be labeled or, depending on the platform, omitted from metadata or +/// platform-specific artifacts. Future refreshPluginsList( FlutterProject project, { bool iosPlatform = false, diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index d6a8384cac..f8de74eda8 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -31,6 +31,17 @@ import '../src/fake_pub_deps.dart'; import '../src/fakes.dart'; void main() { + // TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default. + // See https://github.com/flutter/flutter/issues/160257 for details. + FeatureFlags enableExplicitPackageDependencies() { + return TestFeatureFlags(isExplicitPackageDependenciesEnabled: true); + } + + FeatureFlags disableExplicitPackageDependencies() { + // ignore: avoid_redundant_argument_values + return TestFeatureFlags(isExplicitPackageDependenciesEnabled: false); + } + // TODO(zanderso): remove once FlutterProject is fully refactored. // this is safe since no tests have expectations on the test logger. final BufferLogger logger = BufferLogger.test(); @@ -272,6 +283,107 @@ void main() { await project.regeneratePlatformSpecificTooling(releaseMode: false); expectExists(project.android.hostAppGradleRoot.childFile('local.properties')); }); + + testUsingContext( + '--no-explicit-package-dependencies does not determine dev dependencies', + () async { + // Create a plugin. + await aPluginProject(legacy: false); + // Create a project that depends on that plugin. + final FlutterProject project = await projectWithPluginDependency(); + // Don't bother with Android, we just want the manifest. + project.directory.childDirectory('android').deleteSync(recursive: true); + + await project.regeneratePlatformSpecificTooling(releaseMode: false); + expect( + project.flutterPluginsDependenciesFile.readAsStringSync(), + isNot(contains('"dev_dependency":true')), + ); + }, + overrides: { + FeatureFlags: disableExplicitPackageDependencies, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Pub: () => FakePubWithPrimedDeps(devDependencies: {'my_plugin'}), + FlutterProjectFactory: + () => FlutterProjectFactory(logger: logger, fileSystem: globals.fs), + }, + ); + + testUsingContext( + '--explicit-package-dependencies determines dev dependencies', + () async { + // Create a plugin. + await aPluginProject(legacy: false); + // Create a project that depends on that plugin. + final FlutterProject project = await projectWithPluginDependency(); + // Don't bother with Android, we just want the manifest. + project.directory.childDirectory('android').deleteSync(recursive: true); + + await project.regeneratePlatformSpecificTooling(releaseMode: false); + expect( + project.flutterPluginsDependenciesFile.readAsStringSync(), + contains('"dev_dependency":true'), + ); + }, + overrides: { + FeatureFlags: enableExplicitPackageDependencies, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Pub: () => FakePubWithPrimedDeps(devDependencies: {'my_plugin'}), + FlutterProjectFactory: + () => FlutterProjectFactory(logger: logger, fileSystem: globals.fs), + }, + ); + + testUsingContext( + '--explicit-package-dependencies with releaseMode: false retains dev plugins', + () async { + // Create a plugin. + await aPluginProject(includeAndroidMain: true, legacy: false); + // Create a project that depends on that plugin. + final FlutterProject project = await projectWithPluginDependency(); + + await project.regeneratePlatformSpecificTooling(releaseMode: false); + expect( + project.android.generatedPluginRegistrantFile.readAsStringSync(), + contains('MyPlugin'), + ); + }, + overrides: { + FeatureFlags: enableExplicitPackageDependencies, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Pub: () => FakePubWithPrimedDeps(devDependencies: {'my_plugin'}), + FlutterProjectFactory: + () => FlutterProjectFactory(logger: logger, fileSystem: globals.fs), + }, + ); + + testUsingContext( + '--explicit-package-dependencies with releaseMode: true omits dev plugins', + () async { + // Create a plugin. + await aPluginProject(includeAndroidMain: true, legacy: false); + // Create a project that depends on that plugin. + final FlutterProject project = await projectWithPluginDependency(); + + await project.regeneratePlatformSpecificTooling(releaseMode: true); + expect( + project.android.generatedPluginRegistrantFile.readAsStringSync(), + isNot(contains('MyPlugin')), + ); + }, + overrides: { + FeatureFlags: enableExplicitPackageDependencies, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Pub: () => FakePubWithPrimedDeps(devDependencies: {'my_plugin'}), + FlutterProjectFactory: + () => FlutterProjectFactory(logger: logger, fileSystem: globals.fs), + }, + ); + testUsingContext( 'injects plugins for macOS', () async { @@ -1730,7 +1842,41 @@ Future someProject({ return FlutterProject.fromDirectory(directory); } -Future aPluginProject({bool legacy = true}) async { +Future projectWithPluginDependency() async { + final Directory directory = globals.fs.directory('some_project'); + directory.childDirectory('.dart_tool').childFile('package_config.json') + ..createSync(recursive: true) + ..writeAsStringSync(''' +{ + "configVersion": 2, + "packages": [ + { + "name": "my_plugin", + "rootUri": "/plugin_project", + "packageUri": "lib/", + "languageVersion": "2.12" + } + ] +} +'''); + directory.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(''' +name: app_name +flutter: + +dependencies: + my_plugin: + sdk: flutter +'''); + directory.childDirectory('ios').createSync(recursive: true); + final Directory androidDirectory = directory.childDirectory('android') + ..createSync(recursive: true); + androidDirectory.childFile('AndroidManifest.xml').writeAsStringSync(''); + return FlutterProject.fromDirectory(directory); +} + +Future aPluginProject({bool legacy = true, bool includeAndroidMain = false}) async { final Directory directory = globals.fs.directory('plugin_project'); directory.childDirectory('ios').createSync(recursive: true); directory.childDirectory('android').createSync(recursive: true); @@ -1765,6 +1911,16 @@ flutter: '''; } directory.childFile('pubspec.yaml').writeAsStringSync(pluginPubSpec); + if (includeAndroidMain) { + directory + .childDirectory('android') + .childFile(globals.fs.path.join('src', 'main', 'java', 'com', 'example', 'MyPlugin.java')) + ..createSync(recursive: true) + ..writeAsStringSync(''' +import io.flutter.embedding.engine.plugins.FlutterPlugin; +class MyPlugin extends FluttPlugin { /* ... */ } +'''); + } return FlutterProject.fromDirectory(directory); } diff --git a/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart b/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart index 7f7dd82f05..da967be5c1 100644 --- a/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart +++ b/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart @@ -5,6 +5,7 @@ import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/features.dart'; import '../integration.shard/test_utils.dart'; import '../src/common.dart'; @@ -14,6 +15,20 @@ void main() { setUpAll(() { processManager.runSync([flutterBin, 'config', '--enable-macos-desktop']); + + // TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default. + // See https://github.com/flutter/flutter/issues/160257 for details. + if (!explicitPackageDependencies.master.enabledByDefault) { + processManager.runSync([flutterBin, 'config', '--explicit-package-dependencies']); + } + }); + + tearDownAll(() { + // TODO(matanlurey): Remove after `explicit-package-dependencies` is enabled by default. + // See https://github.com/flutter/flutter/issues/160257 for details. + if (!explicitPackageDependencies.master.enabledByDefault) { + processManager.runSync([flutterBin, 'config', '--no-explicit-package-dependencies']); + } }); for (final String buildMode in ['Debug', 'Release']) {