Invalidate pod install output if .flutter-plugins-dependencies content changes. (#163275)

Closes https://github.com/flutter/flutter/issues/162399.
Closes https://github.com/flutter/flutter/issues/163272.

`refreshPluginsList` is commonly called in iOS/macOS apps, more times
than you expect (and load bearing);
https://github.com/flutter/flutter/issues/157391.

With `--explicit-package-dependencies`, we no longer write (or compare)
the output of `.flutter-plugins`. The iOS/macOS workflows required `pod
install` output to be invalidated when plugins when change, but the
logic that was being used to see if plugins changed _only_ worked for
the `.flutter-plugins` file format.

In the original code:

```txt
# oldPluginsFileStringContent
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"integration_test","path":"/Users/matanl/Developer/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"ios_objc_cocoapods_plugin","path":"/var/folders/qw/qw_3qd1x4kz5w975jhdq4k58007b7h/T/swift_package_manager_enabled.XrEWXS/ios_objc_cocoapods_plugin/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"integration_test","path":"/Users/matanl/Developer/flutter/packages/integration_test/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"integration_test","dependencies":[]},{"name":"ios_objc_cocoapods_plugin","dependencies":[]}],"date_created":"2025-02-13 17:01:11.023097","version":"3.30.0-1.0.pre.163","swift_package_manager_enabled":{"ios":true,"macos":false}}

# pluginsMap
{ios: [{name: integration_test, path: /Users/matanl/Developer/flutter/packages/integration_test/, native_build: true, dependencies: [], dev_dependency: false}, {name: ios_objc_cocoapods_plugin, path: /var/folders/qw/qw_3qd1x4kz5w975jhdq4k58007b7h/T/swift_package_manager_enabled.XrEWXS/ios_objc_cocoapods_plugin/, native_build: true, dependencies: [], dev_dependency: false}], android: [{name: integration_test, path: /Users/matanl/Developer/flutter/packages/integration_test/, native_build: true, dependencies: [], dev_dependency: false}], macos: [], linux: [], windows: [], web: []}
```

As you can see, `pluginsChanged =
oldPluginsFileStringContent.contains(pluginsMap.toString());` was
_always_ `false`, but we never knew because we'd always just fall back
to using the `.flutter-plugins` content comparison (which always
worked).

I added a test as well.

This also appears to fix
https://github.com/flutter/flutter/issues/162399.

/cc @jmagman @jonahwilliams
This commit is contained in:
Matan Lurey 2025-02-18 07:35:07 -08:00 committed by GitHub
parent 5365993ff5
commit 29d1e10a02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 95 additions and 1 deletions

View File

@ -244,7 +244,15 @@ bool _writeFlutterPluginsList(
final String? oldPluginsFileStringContent = _readFileContent(pluginsFile);
bool pluginsChanged = true;
if (oldPluginsFileStringContent != null) {
pluginsChanged = oldPluginsFileStringContent.contains(pluginsMap.toString());
Object? decodedJson;
try {
decodedJson = jsonDecode(oldPluginsFileStringContent);
if (decodedJson is Map<String, Object?>) {
final String jsonOfNewPluginsMap = jsonEncode(pluginsMap);
final String jsonOfOldPluginsMap = jsonEncode(decodedJson[_kFlutterPluginsPluginListKey]);
pluginsChanged = jsonOfNewPluginsMap != jsonOfOldPluginsMap;
}
} on FormatException catch (_) {}
}
final String pluginFileContent = json.encode(result);
pluginsFile.writeAsStringSync(pluginFileContent, flush: true);

View File

@ -20,6 +20,7 @@ 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';
@ -102,6 +103,8 @@ void main() {
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).
@ -190,6 +193,7 @@ void main() {
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);
@ -628,6 +632,78 @@ dependencies:
},
);
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: FakePubWithPrimedDeps.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.
FeatureFlags: enableExplicitPackageDependencies,
},
);
testUsingContext(
'.flutter-plugins-dependencies contains plugin platform info',
() async {
@ -3004,3 +3080,13 @@ class FakeDarwinDependencyManagement extends Fake implements DarwinDependencyMan
setupPlatforms.add(platform);
}
}
/// A fake of [CocoaPods] that writes calls to [invalidatePodInstallOutput] to [capturedInvocations].
final class FakeCocoaPodsCapturesInvalidate extends Fake implements CocoaPods {
final List<XcodeBasedProject> capturedInvocations = <XcodeBasedProject>[];
@override
void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {
capturedInvocations.add(xcodeProject);
}
}