From 7141eb3bdc00c560186d7ffb076d2ee06a6db13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Sharma?= <737941+loic-sharma@users.noreply.github.com> Date: Tue, 29 Apr 2025 12:11:50 -0700 Subject: [PATCH] [tool] Add a telemetry event to track SwiftPM migration (#166773) Flutter is migrating from CocoaPods to Swift Package Manager to manage native dependencies on iOS and macOS. We'd like to answer the following questions: 1. Can we remove CocoaPods support from Flutter's tooling? 2. Can we tell plugin authors that they can remove CocoaPods integration from their plugins? This makes the Flutter tool send an event when it injects plugins into an iOS or macOS project. This will happen whenever a user does commands like `flutter build ios`, `flutter build macos`, and more. Part of https://github.com/flutter/flutter/issues/147602 Depends on: https://github.com/dart-lang/tools/pull/2062 ## 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. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [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 --- .../lib/src/flutter_plugins.dart | 2 + .../macos/darwin_dependency_management.dart | 27 +- .../darwin_dependency_management_test.dart | 487 +++++++++++++----- 3 files changed, 388 insertions(+), 128 deletions(-) diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 94d469dcc1b..9f4704bfdb3 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -1327,7 +1327,9 @@ Future injectPlugins( templateRenderer: globals.templateRenderer, ), fileSystem: globals.fs, + featureFlags: featureFlags, logger: globals.logger, + analytics: globals.analytics, ); if (iosPlatform) { await darwinDependencyManagerSetup.setUp(platform: SupportedPlatform.ios); diff --git a/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart index f35b704a7df..da8e3e91a9f 100644 --- a/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart +++ b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:unified_analytics/unified_analytics.dart'; + import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; +import '../features.dart'; import '../plugins.dart'; import '../project.dart'; import 'cocoapods.dart'; @@ -21,20 +24,26 @@ class DarwinDependencyManagement { required CocoaPods cocoapods, required SwiftPackageManager swiftPackageManager, required FileSystem fileSystem, + required FeatureFlags featureFlags, required Logger logger, + required Analytics analytics, }) : _project = project, _plugins = plugins, _cocoapods = cocoapods, _swiftPackageManager = swiftPackageManager, _fileSystem = fileSystem, - _logger = logger; + _featureFlags = featureFlags, + _logger = logger, + _analytics = analytics; final FlutterProject _project; final List _plugins; final CocoaPods _cocoapods; final SwiftPackageManager _swiftPackageManager; final FileSystem _fileSystem; + final FeatureFlags _featureFlags; final Logger _logger; + final Analytics _analytics; /// Generates/updates required files and project settings for Darwin /// Dependency Managers (CocoaPods and Swift Package Manager). Projects may @@ -87,6 +96,7 @@ class DarwinDependencyManagement { // whether to run. useCocoapods = _plugins.isNotEmpty; } + if (useCocoapods) { await _cocoapods.setupPodfile(xcodeProject); } @@ -95,6 +105,21 @@ class DarwinDependencyManagement { else if (xcodeProject.podfile.existsSync() && xcodeProject.podfileLock.existsSync()) { _cocoapods.addPodsDependencyToFlutterXcconfig(xcodeProject); } + + final Event event = Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: _project.isModule, + swiftPackageManagerUsable: xcodeProject.usesSwiftPackageManager, + swiftPackageManagerFeatureEnabled: _featureFlags.isSwiftPackageManagerEnabled, + projectDisabledSwiftPackageManager: _project.manifest.disabledSwiftPackageManager, + projectHasSwiftPackageManagerIntegration: + xcodeProject.flutterPluginSwiftPackageInProjectSettings, + pluginCount: totalCount, + swiftPackageCount: swiftPackageCount, + podCount: podCount, + ); + + _analytics.send(event); } /// Returns count of total number of plugins, number of Swift Package Manager diff --git a/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart index 84bf12ed6b2..2cf039dddc4 100644 --- a/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart @@ -5,6 +5,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/macos/cocoapods.dart'; import 'package:flutter_tools/src/macos/darwin_dependency_management.dart'; import 'package:flutter_tools/src/macos/swift_package_manager.dart'; @@ -12,8 +13,10 @@ import 'package:flutter_tools/src/platform_plugins.dart'; import 'package:flutter_tools/src/plugins.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; +import '../../src/fakes.dart'; void main() { const List supportedPlatforms = [ @@ -26,16 +29,22 @@ void main() { group('for ${platform.name}', () { group('generatePluginsSwiftPackage', () { testWithoutContext('throw if invalid platform', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(fileSystem: fs), + project: FakeFlutterProject(fileSystem: testFileSystem), plugins: [], cocoapods: FakeCocoaPods(), swiftPackageManager: FakeSwiftPackageManager(), - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(), logger: testLogger, + analytics: testAnalytics, ); await expectLater( @@ -48,9 +57,13 @@ void main() { }); group('when using Swift Package Manager', () { testWithoutContext('with only CocoaPod plugins', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File cocoapodPluginPodspec = fs.file( + final FakeAnalytics fakeAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File cocoapodPluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec', )..createSync(recursive: true); final List plugins = [ @@ -66,26 +79,51 @@ void main() { final FakeCocoaPods cocoaPods = FakeCocoaPods(); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(usesSwiftPackageManager: true, fileSystem: fs), + project: FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: testFileSystem, + ), plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), logger: testLogger, + analytics: fakeAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isTrue); expect(testLogger.warningText, isEmpty); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isTrue); + expect( + fakeAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: false, + pluginCount: 1, + swiftPackageCount: 0, + podCount: 1, + ), + ), + ); }); testWithoutContext( 'with only Swift Package Manager plugins and no pod integration', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File swiftPackagePluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File swiftPackagePluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', )..createSync(recursive: true); final List plugins = [ @@ -99,100 +137,12 @@ void main() { expectedPlugins: plugins, ); final FakeCocoaPods cocoaPods = FakeCocoaPods(); - - final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(usesSwiftPackageManager: true, fileSystem: fs), - plugins: plugins, - cocoapods: cocoaPods, - swiftPackageManager: swiftPackageManager, - fileSystem: fs, - logger: testLogger, - ); - await dependencyManagement.setUp(platform: platform); - expect(swiftPackageManager.generated, isTrue); - expect(testLogger.warningText, isEmpty); - expect(testLogger.statusText, isEmpty); - expect(cocoaPods.podfileSetup, isFalse); - }, - ); - - testWithoutContext( - 'with only Swift Package Manager plugins but project not migrated', - () async { - final MemoryFileSystem fs = MemoryFileSystem(); - final BufferLogger testLogger = BufferLogger.test(); - final File swiftPackagePluginPodspec = fs.file( - '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', - )..createSync(recursive: true); - final List plugins = [ - FakePlugin( - name: 'swift_package_plugin_1', - platforms: {platform.name: FakePluginPlatform()}, - pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, - ), - ]; - final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( - expectedPlugins: plugins, - ); - final File projectPodfile = fs.file('/path/to/Podfile') - ..createSync(recursive: true); - projectPodfile.writeAsStringSync('Standard Podfile template'); - final FakeCocoaPods cocoaPods = FakeCocoaPods(podFile: projectPodfile); - final FakeFlutterProject project = FakeFlutterProject( + final FlutterProject project = FakeFlutterProject( usesSwiftPackageManager: true, - fileSystem: fs, + fileSystem: testFileSystem, ); final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; - xcodeProject.podfile.createSync(recursive: true); - xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); - - final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: project, - plugins: plugins, - cocoapods: cocoaPods, - swiftPackageManager: swiftPackageManager, - fileSystem: fs, - logger: testLogger, - ); - await dependencyManagement.setUp(platform: platform); - expect(swiftPackageManager.generated, isTrue); - expect(testLogger.warningText, isEmpty); - expect(testLogger.statusText, isEmpty); - expect(cocoaPods.podfileSetup, isFalse); - }, - ); - - testWithoutContext( - 'with only Swift Package Manager plugins with preexisting standard CocoaPods Podfile', - () async { - final MemoryFileSystem fs = MemoryFileSystem(); - final BufferLogger testLogger = BufferLogger.test(); - final File swiftPackagePluginPodspec = fs.file( - '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', - )..createSync(recursive: true); - final List plugins = [ - FakePlugin( - name: 'swift_package_plugin_1', - platforms: {platform.name: FakePluginPlatform()}, - pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, - ), - ]; - final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( - expectedPlugins: plugins, - ); - final File projectPodfile = fs.file('/path/to/Podfile') - ..createSync(recursive: true); - projectPodfile.writeAsStringSync('Standard Podfile template'); - final FakeCocoaPods cocoaPods = FakeCocoaPods(podFile: projectPodfile); - final FakeFlutterProject project = FakeFlutterProject( - usesSwiftPackageManager: true, - fileSystem: fs, - ); - final XcodeBasedProject xcodeProject = - platform == SupportedPlatform.ios ? project.ios : project.macos; - xcodeProject.podfile.createSync(recursive: true); - xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); xcodeProject.xcodeProjectInfoFile.writeAsStringSync( 'FlutterGeneratedPluginSwiftPackage', @@ -203,8 +153,151 @@ void main() { plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), logger: testLogger, + analytics: testAnalytics, + ); + await dependencyManagement.setUp(platform: platform); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: true, + pluginCount: 1, + swiftPackageCount: 1, + podCount: 0, + ), + ), + ); + }, + ); + + testWithoutContext( + 'with only Swift Package Manager plugins but project not migrated', + () async { + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File swiftPackagePluginPodspec = testFileSystem.file( + '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', + )..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final File projectPodfile = testFileSystem.file('/path/to/Podfile') + ..createSync(recursive: true); + projectPodfile.writeAsStringSync('Standard Podfile template'); + final FakeCocoaPods cocoaPods = FakeCocoaPods(podFile: projectPodfile); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: testFileSystem, + ); + final XcodeBasedProject xcodeProject = + platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.podfile.createSync(recursive: true); + xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), + logger: testLogger, + analytics: testAnalytics, + ); + await dependencyManagement.setUp(platform: platform); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: false, + pluginCount: 1, + swiftPackageCount: 1, + podCount: 0, + ), + ), + ); + }, + ); + + testWithoutContext( + 'with only Swift Package Manager plugins with preexisting standard CocoaPods Podfile', + () async { + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File swiftPackagePluginPodspec = testFileSystem.file( + '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', + )..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final File projectPodfile = testFileSystem.file('/path/to/Podfile') + ..createSync(recursive: true); + projectPodfile.writeAsStringSync('Standard Podfile template'); + final FakeCocoaPods cocoaPods = FakeCocoaPods(podFile: projectPodfile); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: testFileSystem, + ); + final XcodeBasedProject xcodeProject = + platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.podfile.createSync(recursive: true); + xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); + xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); + xcodeProject.xcodeProjectInfoFile.writeAsStringSync( + 'FlutterGeneratedPluginSwiftPackage', + ); + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), + logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isTrue); @@ -226,15 +319,35 @@ void main() { ); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isFalse); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: true, + pluginCount: 1, + swiftPackageCount: 1, + podCount: 0, + ), + ), + ); }, ); testWithoutContext( 'with only Swift Package Manager plugins with preexisting custom CocoaPods Podfile', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File swiftPackagePluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File swiftPackagePluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', )..createSync(recursive: true); final List plugins = [ @@ -247,13 +360,13 @@ void main() { final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( expectedPlugins: plugins, ); - final File projectPodfile = fs.file('/path/to/Podfile') + final File projectPodfile = testFileSystem.file('/path/to/Podfile') ..createSync(recursive: true); projectPodfile.writeAsStringSync('Standard Podfile template'); final FakeCocoaPods cocoaPods = FakeCocoaPods(podFile: projectPodfile); final FakeFlutterProject project = FakeFlutterProject( usesSwiftPackageManager: true, - fileSystem: fs, + fileSystem: testFileSystem, ); final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; @@ -269,8 +382,10 @@ void main() { plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isTrue); @@ -296,16 +411,36 @@ void main() { ); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isFalse); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: true, + pluginCount: 1, + swiftPackageCount: 1, + podCount: 0, + ), + ), + ); }, ); testWithoutContext('with mixed plugins', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File cocoapodPluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File cocoapodPluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec', )..createSync(recursive: true); - final File swiftPackagePluginPodspec = fs.file( + final File swiftPackagePluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', )..createSync(recursive: true); final List plugins = [ @@ -328,28 +463,60 @@ void main() { expectedPlugins: plugins, ); final FakeCocoaPods cocoaPods = FakeCocoaPods(); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: testFileSystem, + ); + final XcodeBasedProject xcodeProject = + platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); + xcodeProject.xcodeProjectInfoFile.writeAsStringSync( + 'FlutterGeneratedPluginSwiftPackage', + ); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(usesSwiftPackageManager: true, fileSystem: fs), + project: project, plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isTrue); expect(testLogger.warningText, isEmpty); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isTrue); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: true, + pluginCount: 2, + swiftPackageCount: 1, + podCount: 1, + ), + ), + ); }); }); group('when not using Swift Package Manager', () { testWithoutContext('but project already migrated', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File cocoapodPluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File cocoapodPluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec', )..createSync(recursive: true); final List plugins = [ @@ -365,7 +532,7 @@ void main() { final FakeCocoaPods cocoaPods = FakeCocoaPods(); final FakeFlutterProject project = FakeFlutterProject( usesSwiftPackageManager: true, - fileSystem: fs, + fileSystem: testFileSystem, ); final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; @@ -379,20 +546,42 @@ void main() { plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(isSwiftPackageManagerEnabled: true), logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isTrue); expect(testLogger.warningText, isEmpty); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isTrue); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: true, + swiftPackageManagerFeatureEnabled: true, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: true, + pluginCount: 1, + swiftPackageCount: 0, + podCount: 1, + ), + ), + ); }); testWithoutContext('with only CocoaPod plugins', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File cocoapodPluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File cocoapodPluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec', )..createSync(recursive: true); final List plugins = [ @@ -408,24 +597,46 @@ void main() { final FakeCocoaPods cocoaPods = FakeCocoaPods(); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(fileSystem: fs), + project: FakeFlutterProject(fileSystem: testFileSystem), plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(), logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isFalse); expect(testLogger.warningText, isEmpty); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isTrue); + expect( + testAnalytics.sentEvents, + contains( + Event.flutterInjectDarwinPlugins( + platform: platform.name, + isModule: false, + swiftPackageManagerUsable: false, + swiftPackageManagerFeatureEnabled: false, + projectDisabledSwiftPackageManager: false, + projectHasSwiftPackageManagerIntegration: false, + pluginCount: 1, + swiftPackageCount: 0, + podCount: 1, + ), + ), + ); }); testWithoutContext('with only Swift Package Manager plugins', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File swiftPackagePluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File swiftPackagePluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift', )..createSync(recursive: true); final List plugins = [ @@ -441,12 +652,14 @@ void main() { final FakeCocoaPods cocoaPods = FakeCocoaPods(); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(fileSystem: fs), + project: FakeFlutterProject(fileSystem: testFileSystem), plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(), logger: testLogger, + analytics: testAnalytics, ); await expectLater( () => dependencyManagement.setUp(platform: platform), @@ -460,12 +673,17 @@ void main() { ); expect(swiftPackageManager.generated, isFalse); expect(cocoaPods.podfileSetup, isFalse); + expect(testAnalytics.sentEvents, isEmpty); }); testWithoutContext('when project is a module', () async { - final MemoryFileSystem fs = MemoryFileSystem(); + final MemoryFileSystem testFileSystem = MemoryFileSystem.test(); final BufferLogger testLogger = BufferLogger.test(); - final File cocoapodPluginPodspec = fs.file( + final FakeAnalytics testAnalytics = getInitializedFakeAnalyticsInstance( + fs: testFileSystem, + fakeFlutterVersion: FakeFlutterVersion(), + ); + final File cocoapodPluginPodspec = testFileSystem.file( '/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec', )..createSync(recursive: true); final List plugins = [ @@ -481,18 +699,21 @@ void main() { final FakeCocoaPods cocoaPods = FakeCocoaPods(); final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( - project: FakeFlutterProject(fileSystem: fs, isModule: true), + project: FakeFlutterProject(fileSystem: testFileSystem, isModule: true), plugins: plugins, cocoapods: cocoaPods, swiftPackageManager: swiftPackageManager, - fileSystem: fs, + fileSystem: testFileSystem, + featureFlags: TestFeatureFlags(), logger: testLogger, + analytics: testAnalytics, ); await dependencyManagement.setUp(platform: platform); expect(swiftPackageManager.generated, isFalse); expect(testLogger.warningText, isEmpty); expect(testLogger.statusText, isEmpty); expect(cocoaPods.podfileSetup, isFalse); + expect(testAnalytics.sentEvents, isEmpty); }); }); }); @@ -571,15 +792,24 @@ class FakeMacOSProject extends Fake implements MacOSProject { File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig'); } +class FakeFlutterManifest extends Fake implements FlutterManifest { + FakeFlutterManifest({this.disabledSwiftPackageManager = false}); + + @override + final bool disabledSwiftPackageManager; +} + class FakeFlutterProject extends Fake implements FlutterProject { FakeFlutterProject({ required this.fileSystem, this.usesSwiftPackageManager = false, this.isModule = false, - }); + FlutterManifest? manifest, + }) : _manifest = manifest ?? FakeFlutterManifest(); final MemoryFileSystem fileSystem; final bool usesSwiftPackageManager; + final FlutterManifest _manifest; @override late final FakeIosProject ios = FakeIosProject( @@ -595,6 +825,9 @@ class FakeFlutterProject extends Fake implements FlutterProject { @override final bool isModule; + + @override + FlutterManifest get manifest => _manifest; } class FakeSwiftPackageManager extends Fake implements SwiftPackageManager {