From fa04f4a8d2c02fd981b9b68e50f0f5b899da582f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Sharma?= <737941+loic-sharma@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:13:15 -0800 Subject: [PATCH] [SwiftPM] Make 'flutter build ios-framework' generate an empty Package.swift (#161464) ### Background Flutter generates a `Package.swift` file that contains the plugins that should be built using SwiftPM. Since Flutter does not support Swift Package Manager in add-to-app scenarios yet, Flutter uses CocoaPods to build plugins if you call `flutter build ios-framework`. To ensure `pod install` is rerun, the tool deleted the `Package.swift` file. Unfortunately, deleting the `Package.swift` file causes the Xcode project to no longer build if it has SwiftPM integration. This caused the [`build_ios_framework_module_test` to fail when SwiftPM was turned on by default](https://ci.chromium.org/ui/p/flutter/builders/prod/Mac%20build_ios_framework_module_test/21807/overview). ### Fix Instead of deleting the `Package.swift` file, we instead generate a `Package.swift` file with no dependencies. This will invalidate the fingerprint and cause `pod install` to rerun if the app had plugins that were previously built using SwiftPM. ## 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/macos/cocoapod_utils.dart | 14 ++++++++-- .../macos/cocoapod_utils_test.dart | 23 ++++++++++++--- .../swift_package_manager_test.dart | 28 ++++++++++--------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart index b9dfe29733..2547245958 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../base/error_handling_io.dart'; import '../base/fingerprint.dart'; import '../build_info.dart'; import '../cache.dart'; import '../flutter_plugins.dart'; import '../globals.dart' as globals; +import '../plugins.dart'; import '../project.dart'; +import 'swift_package_manager.dart'; /// For a given build, determines whether dependencies have changed since the /// last call to processPods, then calls processPods with that information. @@ -61,8 +62,15 @@ Future processPodsIfNeeded( await globals.cocoaPods?.setupPodfile(xcodeProject); } - // Delete Swift Package Manager manifest to invalidate fingerprinter - ErrorHandlingFileSystem.deleteIfExists(xcodeProject.flutterPluginSwiftPackageManifest); + // Generate an empty Swift Package Manager manifest to invalidate fingerprinter + final SwiftPackageManager swiftPackageManager = SwiftPackageManager( + fileSystem: globals.localFileSystem, + templateRenderer: globals.templateRenderer, + ); + final SupportedPlatform platform = + xcodeProject is IosProject ? SupportedPlatform.ios : SupportedPlatform.macos; + + await swiftPackageManager.generatePluginsSwiftPackage(const [], platform, xcodeProject); } // If the Xcode project, Podfile, generated plugin Swift Package, or podhelper diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart index 240ded035c..2c3ab180b2 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/dart/pub.dart'; @@ -239,7 +240,8 @@ void main() { setUpProject(flutterProject, fs); createFakePlugins(flutterProject, fs, ['plugin_one', 'plugin_two']); flutterProject.ios.usesSwiftPackageManager = true; - flutterProject.ios.flutterPluginSwiftPackageManifest.createSync(recursive: true); + final File generatedManifestFile = flutterProject.ios.flutterPluginSwiftPackageManifest; + generatedManifestFile.createSync(recursive: true); await processPodsIfNeeded( flutterProject.ios, @@ -254,7 +256,9 @@ void main() { 'Swift Package Manager does not yet support this command. ' 'CocoaPods will be used instead.\n', ); - expect(flutterProject.ios.flutterPluginSwiftPackageManifest.existsSync(), isFalse); + expect(generatedManifestFile, exists); + const String emptyDependencies = 'dependencies: [\n \n ],\n'; + expect(generatedManifestFile.readAsStringSync(), contains(emptyDependencies)); }, overrides: { FileSystem: () => fs, @@ -412,7 +416,9 @@ void main() { setUpProject(flutterProject, fs); createFakePlugins(flutterProject, fs, ['plugin_one', 'plugin_two']); flutterProject.macos.usesSwiftPackageManager = true; - flutterProject.macos.flutterPluginSwiftPackageManifest.createSync(recursive: true); + final File generatedManifestFile = + flutterProject.macos.flutterPluginSwiftPackageManifest; + generatedManifestFile.createSync(recursive: true); await processPodsIfNeeded( flutterProject.macos, @@ -427,7 +433,10 @@ void main() { 'Swift Package Manager does not yet support this command. ' 'CocoaPods will be used instead.\n', ); - expect(flutterProject.macos.flutterPluginSwiftPackageManifest.existsSync(), isFalse); + + expect(generatedManifestFile, exists); + const String emptyDependencies = 'dependencies: [\n \n ],\n'; + expect(generatedManifestFile.readAsStringSync(), contains(emptyDependencies)); }, overrides: { FileSystem: () => fs, @@ -517,6 +526,9 @@ class FakeMacOSProject extends Fake implements MacOSProject { @override bool usesSwiftPackageManager = false; + + @override + bool get flutterPluginSwiftPackageInProjectSettings => usesSwiftPackageManager; } class FakeIosProject extends Fake implements IosProject { @@ -555,6 +567,9 @@ class FakeIosProject extends Fake implements IosProject { @override bool usesSwiftPackageManager = false; + + @override + bool get flutterPluginSwiftPackageInProjectSettings => usesSwiftPackageManager; } class FakeAndroidProject extends Fake implements AndroidProject { diff --git a/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart b/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart index fef95beb4f..45ba3b1453 100644 --- a/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart +++ b/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart @@ -507,19 +507,21 @@ void main() { expect(fileSystem.directory(appDirectoryPath).childDirectory('.ios'), isNot(exists)); - // TODO(loic-sharma): A Swift package manifest should not be generated. - // https://github.com/flutter/flutter/issues/146957 - // expect( - // fileSystem - // .directory(appDirectoryPath) - // .childDirectory('ios') - // .childDirectory('Flutter') - // .childDirectory('ephemeral') - // .childDirectory('Packages') - // .childDirectory('FlutterGeneratedPluginSwiftPackage') - // .childFile('Package.swift'), - // isFalse, - // ); + // Verify the generated Swift Package Manager manifest file has no dependencies. + final File generatedManifestFile = fileSystem + .directory(appDirectoryPath) + .childDirectory('ios') + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .childFile('Package.swift'); + + expect(generatedManifestFile, exists); + + final String generatedManifest = generatedManifestFile.readAsStringSync(); + const String expected = 'dependencies: [\n \n ],\n'; + expect(generatedManifest, contains(expected)); expect( fileSystem