From 63f9060fb030d9f4982bac6671e863383a5c9196 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Mon, 19 May 2025 10:27:33 -0500 Subject: [PATCH] Make FlutterGeneratedPluginSwiftPackage an Xcode root package (#168789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR makes the `FlutterGeneratedPluginSwiftPackage` swift package an Xcode root package. A root package is one that is within the Xcode project as a file reference. See image below: ![Screenshot 2025-05-14 at 2 54 28 PM](https://github.com/user-attachments/assets/c7cd7738-0f1a-4409-a7d1-f3d914a840d9) This change makes it so that when the `FlutterGeneratedPluginSwiftPackage` changes, if Xcode is also open, Xcode will automatically re-resolve the package. This makes Xcode use the new version instead of whatever it had cached. Fixes https://github.com/flutter/flutter/issues/162399 and incremental change towards https://github.com/flutter/flutter/issues/166489. ## 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 --- packages/flutter_tools/lib/src/ios/mac.dart | 1 - .../lib/src/macos/build_macos.dart | 1 - .../lib/src/macos/swift_package_manager.dart | 14 +- ...package_manager_integration_migration.dart | 239 ++++- .../flutter_tools/lib/src/xcode_project.dart | 5 +- .../Runner.xcodeproj/project.pbxproj.tmpl | 6 + .../Runner.xcodeproj/project.pbxproj.tmpl | 6 + .../Runner.xcodeproj/project.pbxproj.tmpl | 6 + ...ge_manager_integration_migration_test.dart | 827 ++++++++++++++++-- 9 files changed, 992 insertions(+), 113 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 6abd5950ba4..97904dd30f7 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -165,7 +165,6 @@ Future buildXcodeProject({ logger: globals.logger, fileSystem: globals.fs, plistParser: globals.plistParser, - features: featureFlags, ), SwiftPackageManagerGitignoreMigration(project, globals.logger), MetalAPIValidationMigrator.ios(app.project, globals.logger), diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 1bad0ad5b35..28e63306918 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -102,7 +102,6 @@ Future buildMacOS({ logger: globals.logger, fileSystem: globals.fs, plistParser: globals.plistParser, - features: featureFlags, ), SwiftPackageManagerGitignoreMigration(flutterProject, globals.logger), MetalAPIValidationMigrator.macos(flutterProject.macos, globals.logger), diff --git a/packages/flutter_tools/lib/src/macos/swift_package_manager.dart b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart index 070f9616f9e..a77bd78b506 100644 --- a/packages/flutter_tools/lib/src/macos/swift_package_manager.dart +++ b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart @@ -10,6 +10,10 @@ import '../plugins.dart'; import '../project.dart'; import 'swift_packages.dart'; +/// The name of the Swift package that's generated by the Flutter tool to add +/// dependencies on Flutter plugin swift packages. +const String kFlutterGeneratedPluginSwiftPackageName = 'FlutterGeneratedPluginSwiftPackage'; + /// Swift Package Manager is a dependency management solution for iOS and macOS /// applications. /// @@ -28,8 +32,6 @@ class SwiftPackageManager { final FileSystem _fileSystem; final TemplateRenderer _templateRenderer; - static const String _defaultFlutterPluginsSwiftPackageName = 'FlutterGeneratedPluginSwiftPackage'; - static final SwiftPackageSupportedPlatform iosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform( platform: SwiftPackagePlatform.ios, @@ -76,19 +78,19 @@ class SwiftPackageManager { // FlutterGeneratedPluginSwiftPackage must be statically linked to ensure // any dynamic dependencies are linked to Runner and prevent undefined symbols. final SwiftPackageProduct generatedProduct = SwiftPackageProduct( - name: _defaultFlutterPluginsSwiftPackageName, - targets: [_defaultFlutterPluginsSwiftPackageName], + name: kFlutterGeneratedPluginSwiftPackageName, + targets: [kFlutterGeneratedPluginSwiftPackageName], libraryType: SwiftPackageLibraryType.static, ); final SwiftPackageTarget generatedTarget = SwiftPackageTarget.defaultTarget( - name: _defaultFlutterPluginsSwiftPackageName, + name: kFlutterGeneratedPluginSwiftPackageName, dependencies: targetDependencies, ); final SwiftPackage pluginsPackage = SwiftPackage( manifest: project.flutterPluginSwiftPackageManifest, - name: _defaultFlutterPluginsSwiftPackageName, + name: kFlutterGeneratedPluginSwiftPackageName, platforms: [swiftSupportedPlatform], products: [generatedProduct], dependencies: packageDependencies, diff --git a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart index 3bb8696ab81..a61d0e124f3 100644 --- a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart +++ b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart @@ -11,9 +11,9 @@ import '../base/logger.dart'; import '../base/project_migrator.dart'; import '../build_info.dart'; import '../convert.dart'; -import '../features.dart'; import '../ios/plist_parser.dart'; import '../ios/xcodeproj.dart'; +import '../macos/swift_package_manager.dart'; import '../project.dart'; /// Swift Package Manager integration requires changes to the Xcode project's @@ -27,7 +27,6 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { required Logger logger, required FileSystem fileSystem, required PlistParser plistParser, - required FeatureFlags features, }) : _xcodeProject = project, _platform = platform, _buildInfo = buildInfo, @@ -35,17 +34,15 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { _xcodeProjectInterpreter = xcodeProjectInterpreter, _fileSystem = fileSystem, _plistParser = plistParser, - _features = features, super(logger); final XcodeBasedProject _xcodeProject; final SupportedPlatform _platform; - final BuildInfo _buildInfo; + final BuildInfo? _buildInfo; final XcodeProjectInterpreter _xcodeProjectInterpreter; final FileSystem _fileSystem; final File _xcodeProjectInfoFile; final PlistParser _plistParser; - final FeatureFlags _features; /// New identifier for FlutterGeneratedPluginSwiftPackage PBXBuildFile. static const String _flutterPluginsSwiftPackageBuildFileIdentifier = '78A318202AECB46A00862997'; @@ -58,6 +55,15 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { static const String _flutterPluginsSwiftPackageProductDependencyIdentifier = '78A3181F2AECB46A00862997'; + /// New identifier for FlutterGeneratedPluginSwiftPackage PBXFileReference. + static const String _flutterPluginsSwiftPackageFileIdentifer = '78E0A7A72DC9AD7400C4905E'; + + /// Existing iOS identifer for Flutter PBXGroup. + static const String _iosFlutterGroupIdentifier = '9740EEB11CF90186004384FC'; + + /// Existing macOS identifer for Flutter PBXGroup. + static const String _macosFlutterGroupIdentifier = '33CEB47122A05771004F2AC0'; + /// Existing iOS identifier for Runner PBXFrameworksBuildPhase. static const String _iosRunnerFrameworksBuildPhaseIdentifier = '97C146EB1CF9000F007C117D'; @@ -95,6 +101,22 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { return _platform == SupportedPlatform.ios ? _iosProjectIdentifier : _macosProjectIdentifier; } + String get _flutterGroupIdentifier { + return _platform == SupportedPlatform.ios + ? _iosFlutterGroupIdentifier + : _macosFlutterGroupIdentifier; + } + + /// The leading path for the `PBXFileReference` relative to the Flutter `PBXGroup`. + /// + /// The actual location for both iOS and macOS is `Flutter/ephemeral`. However, + /// including the `Flutter/` prefix for macOS will cause it to resolve to + /// `Flutter/Flutter/ephemeral`. This is likely due to the macOS Flutter `PBXGroup` + /// using `path` whereas the iOS Flutter `PBXGroup` uses `name`. + String get _relativeEphemeralPath { + return _platform == SupportedPlatform.ios ? 'Flutter/ephemeral' : 'ephemeral'; + } + void restoreFromBackup(SchemeInfo? schemeInfo) { if (backupProjectSettings.existsSync()) { logger.printTrace('Restoring project settings from backup file...'); @@ -110,7 +132,7 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { /// will revert any changes made and throw an error. @override Future migrate() async { - if (!_features.isSwiftPackageManagerEnabled) { + if (!_xcodeProject.usesSwiftPackageManager) { logger.printTrace( 'The Swift Package Manager feature is off. ' 'Skipping the migration that adds Swift Package Manager integration...', @@ -139,7 +161,7 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { // Check for specific strings in the xcscheme and pbxproj to see if the // project has been already migrated, whether automatically or manually. final bool isSchemeMigrated = _isSchemeMigrated(schemeInfo); - final bool isPbxprojMigrated = _xcodeProject.flutterPluginSwiftPackageInProjectSettings; + final bool isPbxprojMigrated = _quickCheckIsPbxprojMigrated(_xcodeProjectInfoFile); if (isSchemeMigrated && isPbxprojMigrated) { return; } @@ -355,6 +377,20 @@ $newContent } } + /// Check if the project has had migrations performed already. + bool _quickCheckIsPbxprojMigrated(File xcodeProjectInfoFile) { + // Initial migration added the `FlutterGeneratedPluginSwiftPackage` and other settings to the pbxproj file. + final bool initialMigrationComplete = _xcodeProject.flutterPluginSwiftPackageInProjectSettings; + + // Secondary migration added the `FlutterGeneratedPluginSwiftPackage` as a root package (via PBXFileReference) + final bool rootFlutterGeneratedPluginSwiftPackageMigrationComplete = xcodeProjectInfoFile + .readAsStringSync() + .contains( + '$_flutterPluginsSwiftPackageFileIdentifer /* $kFlutterGeneratedPluginSwiftPackageName */ = {isa = PBXFileReference', + ); + return initialMigrationComplete && rootFlutterGeneratedPluginSwiftPackageMigrationComplete; + } + /// Checks if all sections have been migrated. If [logErrorIfNotMigrated] is /// true, will log an error for each section that is not migrated. bool _isPbxprojMigratedCorrectly( @@ -365,10 +401,21 @@ $newContent projectInfo, logErrorIfNotMigrated: logErrorIfNotMigrated, ); + final bool packageFileReferenceMigrated = _isFileReferenceMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + identifer: _flutterPluginsSwiftPackageFileIdentifer, + name: kFlutterGeneratedPluginSwiftPackageName, + ); final bool frameworksBuildPhaseMigrated = _isFrameworksBuildPhaseMigrated( projectInfo, logErrorIfNotMigrated: logErrorIfNotMigrated, ); + final bool groupPluginPackageMigrated = _isGroupMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + fileReferenceIdentifier: _flutterPluginsSwiftPackageFileIdentifer, + ); final bool nativeTargetsMigrated = _isNativeTargetMigrated( projectInfo, logErrorIfNotMigrated: logErrorIfNotMigrated, @@ -386,7 +433,9 @@ $newContent logErrorIfNotMigrated: logErrorIfNotMigrated, ); return buildFilesMigrated && + packageFileReferenceMigrated && frameworksBuildPhaseMigrated && + groupPluginPackageMigrated && nativeTargetsMigrated && projectObjectMigrated && localSwiftPackageMigrated && @@ -403,7 +452,19 @@ $newContent List lines = LineSplitter.split(originalProjectContents).toList(); lines = _migrateBuildFile(lines, parsedInfo); + lines = _migrateFileReference( + lines, + parsedInfo, + _flutterPluginsSwiftPackageFileIdentifer, + kFlutterGeneratedPluginSwiftPackageName, + ); lines = _migrateFrameworksBuildPhase(lines, parsedInfo); + lines = _migrateGroup( + lines, + parsedInfo, + _flutterPluginsSwiftPackageFileIdentifer, + kFlutterGeneratedPluginSwiftPackageName, + ); lines = _migrateNativeTarget(lines, parsedInfo); lines = _migrateProjectObject(lines, parsedInfo); lines = _migrateLocalPackageProductDependencies(lines, parsedInfo); @@ -419,15 +480,32 @@ $newContent } void _ensureNewIdentifiersNotUsed(String originalProjectContents) { - if (originalProjectContents.contains(_flutterPluginsSwiftPackageBuildFileIdentifier)) { + if (!originalProjectContents.contains( + '$_flutterPluginsSwiftPackageBuildFileIdentifier /* $kFlutterGeneratedPluginSwiftPackageName in Frameworks */', + ) && + originalProjectContents.contains(_flutterPluginsSwiftPackageBuildFileIdentifier)) { throw Exception('Duplicate id found for PBXBuildFile.'); } - if (originalProjectContents.contains(_flutterPluginsSwiftPackageProductDependencyIdentifier)) { + if (!originalProjectContents.contains( + '$_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */', + ) && + originalProjectContents.contains(_flutterPluginsSwiftPackageProductDependencyIdentifier)) { throw Exception('Duplicate id found for XCSwiftPackageProductDependency.'); } - if (originalProjectContents.contains(_localFlutterPluginsSwiftPackageReferenceIdentifier)) { + if (!originalProjectContents.contains( + '$_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference', + ) && + originalProjectContents.contains(_localFlutterPluginsSwiftPackageReferenceIdentifier)) { throw Exception('Duplicate id found for XCLocalSwiftPackageReference.'); } + if (!originalProjectContents.contains( + '$_flutterPluginsSwiftPackageFileIdentifer /* $kFlutterGeneratedPluginSwiftPackageName */', + ) && + originalProjectContents.contains(_flutterPluginsSwiftPackageFileIdentifer)) { + throw Exception( + 'Duplicate id found for $kFlutterGeneratedPluginSwiftPackageName PBXFileReference.', + ); + } } bool _isBuildFilesMigrated(ParsedProjectInfo projectInfo, {bool logErrorIfNotMigrated = false}) { @@ -447,7 +525,7 @@ $newContent } const String newContent = - ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = $_flutterPluginsSwiftPackageProductDependencyIdentifier /* FlutterGeneratedPluginSwiftPackage */; };'; + ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* $kFlutterGeneratedPluginSwiftPackageName in Frameworks */ = {isa = PBXBuildFile; productRef = $_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */; };'; final (int _, int endSectionIndex) = _sectionRange('PBXBuildFile', lines); @@ -455,6 +533,39 @@ $newContent return lines; } + bool _isFileReferenceMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + required String identifer, + required String name, + }) { + final bool migrated = projectInfo.fileReferenceIdentifiers.contains(identifer); + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXFileReference for $name was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateFileReference( + List lines, + ParsedProjectInfo projectInfo, + String identifier, + String name, + ) { + if (_isFileReferenceMigrated(projectInfo, identifer: identifier, name: name)) { + logger.printTrace('PBXFileReference already migrated. Skipping...'); + return lines; + } + + final String newContent = + ' $identifier /* $name */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = $name; path = $_relativeEphemeralPath/Packages/$name; sourceTree = ""; };'; + + final (int _, int endSectionIndex) = _sectionRange('PBXFileReference', lines); + + lines.insert(endSectionIndex, newContent); + return lines; + } + bool _isFrameworksBuildPhaseMigrated( ParsedProjectInfo projectInfo, { bool logErrorIfNotMigrated = false, @@ -519,7 +630,7 @@ $newContent // If files is null, the files field is missing and must be added. const String newContent = ''' files = ( - $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + $_flutterPluginsSwiftPackageBuildFileIdentifier /* $kFlutterGeneratedPluginSwiftPackageName in Frameworks */, );'''; lines.insert(runnerFrameworksPhaseStartIndex + 1, newContent); } else { @@ -534,7 +645,7 @@ $newContent ); } const String newContent = - ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */,'; + ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* $kFlutterGeneratedPluginSwiftPackageName in Frameworks */,'; lines.insert(startFilesIndex + 1, newContent); } @@ -601,7 +712,7 @@ $newContent // If packageProductDependencies is null, the packageProductDependencies field is missing and must be added. const List newContent = [ ' packageProductDependencies = (', - ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* FlutterGeneratedPluginSwiftPackage */,', + ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */,', ' );', ]; lines.insertAll(runnerNativeTargetStartIndex + 1, newContent); @@ -618,12 +729,88 @@ $newContent ); } const String newContent = - ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* FlutterGeneratedPluginSwiftPackage */,'; + ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */,'; lines.insert(packageProductDependenciesIndex + 1, newContent); } return lines; } + bool _isGroupMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + required String fileReferenceIdentifier, + }) { + final bool migrated = + projectInfo.parsedGroups + .where( + (ParsedProjectGroup group) => + group.identifier == _flutterGroupIdentifier && + group.children != null && + group.children!.contains(fileReferenceIdentifier), + ) + .toList() + .isNotEmpty; + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXGroup was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateGroup( + List lines, + ParsedProjectInfo projectInfo, + String fileReferenceIdentifier, + String fileReferenceName, + ) { + if (_isGroupMigrated(projectInfo, fileReferenceIdentifier: fileReferenceIdentifier)) { + logger.printTrace('PBXGroup already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange('PBXGroup', lines); + + // Find index where Flutter group begins. + final int flutterGroupStartIndex = lines.indexWhere( + (String line) => line.trim().startsWith('$_flutterGroupIdentifier /* Flutter */ = {'), + startSectionIndex, + ); + if (flutterGroupStartIndex == -1 || flutterGroupStartIndex > endSectionIndex) { + throw Exception('Unable to find Flutter PBXGroup.'); + } + + // Get the Flutter Group from the parsed project info. + final ParsedProjectGroup? parsedGroup = + projectInfo.parsedGroups + .where((ParsedProjectGroup group) => group.identifier == _flutterGroupIdentifier) + .toList() + .firstOrNull; + if (parsedGroup == null) { + throw Exception('Unable to find parsed Flutter PBXGroup.'); + } + + if (parsedGroup.children == null) { + // If children is null, the children field is missing and must be added. + final String newContent = ''' + children = ( + $fileReferenceIdentifier /* $fileReferenceName */, + );'''; + lines.insert(flutterGroupStartIndex + 1, newContent); + } else { + // Find the children field within the Flutter PBXGroup. + final int startChildrenIndex = lines.indexWhere( + (String line) => line.trim().contains('children = ('), + flutterGroupStartIndex, + ); + if (startChildrenIndex == -1 || startChildrenIndex > endSectionIndex) { + throw Exception('Unable to children for Flutter PBXGroup.'); + } + final String newContent = ' $fileReferenceIdentifier /* $fileReferenceName */,'; + lines.insert(startChildrenIndex + 1, newContent); + } + + return lines; + } + bool _isProjectObjectMigrated( ParsedProjectInfo projectInfo, { bool logErrorIfNotMigrated = false, @@ -675,9 +862,9 @@ $newContent if (projectObject.packageReferences == null) { // If packageReferences is null, the packageReferences field is missing and must be added. - const List newContent = [ + final List newContent = [ ' packageReferences = (', - ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,', + ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName" */,', ' );', ]; lines.insertAll(projectStartIndex + 1, newContent); @@ -693,7 +880,7 @@ $newContent ); } const String newContent = - ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,'; + ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName" */,'; lines.insert(packageReferencesIndex + 1, newContent); } return lines; @@ -733,9 +920,9 @@ $newContent // There isn't a XCLocalSwiftPackageReference section yet, so add it final List newContent = [ '/* Begin XCLocalSwiftPackageReference section */', - ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {', + ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName" */ = {', ' isa = XCLocalSwiftPackageReference;', - ' relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;', + ' relativePath = Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName;', ' };', '/* End XCLocalSwiftPackageReference section */', ]; @@ -750,9 +937,9 @@ $newContent } final List newContent = [ - ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {', + ' $_localFlutterPluginsSwiftPackageReferenceIdentifier /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName" */ = {', ' isa = XCLocalSwiftPackageReference;', - ' relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;', + ' relativePath = Flutter/ephemeral/Packages/$kFlutterGeneratedPluginSwiftPackageName;', ' };', ]; @@ -795,9 +982,9 @@ $newContent // There isn't a XCSwiftPackageProductDependency section yet, so add it final List newContent = [ '/* Begin XCSwiftPackageProductDependency section */', - ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* FlutterGeneratedPluginSwiftPackage */ = {', + ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */ = {', ' isa = XCSwiftPackageProductDependency;', - ' productName = FlutterGeneratedPluginSwiftPackage;', + ' productName = $kFlutterGeneratedPluginSwiftPackageName;', ' };', '/* End XCSwiftPackageProductDependency section */', ]; @@ -812,9 +999,9 @@ $newContent } final List newContent = [ - ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* FlutterGeneratedPluginSwiftPackage */ = {', + ' $_flutterPluginsSwiftPackageProductDependencyIdentifier /* $kFlutterGeneratedPluginSwiftPackageName */ = {', ' isa = XCSwiftPackageProductDependency;', - ' productName = FlutterGeneratedPluginSwiftPackage;', + ' productName = $kFlutterGeneratedPluginSwiftPackageName;', ' };', ]; diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index 4e04acf10c2..8e9c98a884d 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -21,6 +21,7 @@ import 'ios/code_signing.dart'; import 'ios/plist_parser.dart'; import 'ios/xcode_build_settings.dart' as xcode; import 'ios/xcodeproj.dart'; +import 'macos/swift_package_manager.dart'; import 'macos/xcode.dart'; import 'platform_plugins.dart'; import 'project.dart'; @@ -142,7 +143,7 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { /// dependencies. Directory get flutterPluginSwiftPackageDirectory => ephemeralDirectory .childDirectory('Packages') - .childDirectory('FlutterGeneratedPluginSwiftPackage'); + .childDirectory(kFlutterGeneratedPluginSwiftPackageName); /// The Flutter generated Swift Package manifest (Package.swift) for plugin /// dependencies. @@ -153,7 +154,7 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { /// project's build settings by checking the contents of the pbxproj. bool get flutterPluginSwiftPackageInProjectSettings { return xcodeProjectInfoFile.existsSync() && - xcodeProjectInfoFile.readAsStringSync().contains('FlutterGeneratedPluginSwiftPackage'); + xcodeProjectInfoFile.readAsStringSync().contains(kFlutterGeneratedPluginSwiftPackageName); } /// True if this project doesn't have Swift Package Manager disabled in the diff --git a/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index fbe77665db2..4b014782bd5 100644 --- a/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -49,6 +49,9 @@ 331C80F1294D02FB00263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80F3294D02FB00263BE5 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + {{/withSwiftPackageManager}} 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -94,6 +97,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + {{/withSwiftPackageManager}} 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, diff --git a/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index a34f9388015..db61f31cfb7 100644 --- a/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -50,6 +50,9 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + {{/withSwiftPackageManager}} 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -85,6 +88,9 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + {{/withSwiftPackageManager}} 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, diff --git a/packages/flutter_tools/templates/app/macos.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app/macos.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index 183884fbef9..9129b6b8452 100644 --- a/packages/flutter_tools/templates/app/macos.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/app/macos.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -79,6 +79,9 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + {{/withSwiftPackageManager}} 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -157,6 +160,9 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + {{#withSwiftPackageManager}} + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + {{/withSwiftPackageManager}} 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, diff --git a/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart index 0fcc50df98d..875893b3050 100644 --- a/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart @@ -15,7 +15,6 @@ import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; -import '../../src/fakes.dart'; const List supportedPlatforms = [ SupportedPlatform.ios, @@ -23,29 +22,27 @@ const List supportedPlatforms = [ ]; void main() { - final TestFeatureFlags swiftPackageManagerFullyEnabledFlags = TestFeatureFlags( - isSwiftPackageManagerEnabled: true, - ); - group('Flutter Package Migration', () { testWithoutContext('skips if swift package manager is off', () async { final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + usesSwiftPackageManager: false, + ); + project.flutterPluginSwiftPackageManifest.createSync(recursive: true); final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( - FakeXcodeProject( - platform: SupportedPlatform.ios.name, - fileSystem: memoryFileSystem, - logger: testLogger, - ), + project, SupportedPlatform.ios, BuildInfo.debug, xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: TestFeatureFlags(), ); await projectMigration.migrate(); expect( @@ -72,7 +69,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect( @@ -103,7 +99,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -134,7 +129,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -164,7 +158,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -194,7 +187,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -225,7 +217,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -261,7 +252,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.traceText, isEmpty); @@ -300,7 +290,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: _plutilOutput(settingsAsJsonBeforeMigration)), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater(() => projectMigration.migrate(), throwsToolExit()); expect(testLogger.traceText, contains('Runner.xcscheme already migrated. Skipping...')); @@ -330,7 +319,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -370,7 +358,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( @@ -411,7 +398,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( @@ -452,7 +438,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( @@ -484,7 +469,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( @@ -516,7 +500,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( @@ -551,7 +534,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); @@ -586,7 +568,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); @@ -622,7 +603,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); @@ -665,7 +645,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: _plutilOutput(settingsAsJsonBeforeMigration)), - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.traceText, contains('project.pbxproj already migrated. Skipping...')); @@ -691,7 +670,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -718,7 +696,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: '[]'), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -745,7 +722,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: 'this is not json'), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -775,7 +751,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -803,7 +778,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -831,7 +805,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -862,7 +835,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: _plutilOutput([])), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -904,7 +876,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: _plutilOutput([])), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -947,7 +918,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: FakePlistParser(json: _plutilOutput([])), - features: swiftPackageManagerFullyEnabledFlags, ); expect( () => projectMigration.migrate(), @@ -994,7 +964,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1011,6 +980,187 @@ void main() { }); }); + group('migrate PBXFileReference', () { + testWithoutContext('fails if missing Begin PBXFileReference section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_fileReferenceSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_fileReferenceSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXFileReference section'), + ); + }); + + testWithoutContext('fails if missing End PBXFileReference section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_fileReferenceSectionIndex] = ''' +/* Begin PBXFileReference section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_fileReferenceSectionIndex] = + unmigratedFileReferenceAsJson; + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(json: _plutilOutput([])), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find end of PBXFileReference section'), + ); + }); + + testWithoutContext('fails if End before Begin for PBXFileReference section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_fileReferenceSectionIndex] = ''' +/* End PBXFileReference section */ +/* Begin PBXFileReference section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_fileReferenceSectionIndex] = + unmigratedFileReferenceAsJson; + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(json: _plutilOutput([])), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit( + message: 'Found the end of PBXFileReference section before the beginning.', + ), + ); + }); + + testWithoutContext('successfully added', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_fileReferenceSectionIndex] = unmigratedFileReferenceSection; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_fileReferenceSectionIndex] = + unmigratedFileReferenceAsJson; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXFileReference already migrated. Skipping...'), + isFalse, + ); + settingsBeforeMigration[_fileReferenceSectionIndex] = migratedFileReferenceSection( + platform, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(settingsBeforeMigration), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + group('migrate PBXFrameworksBuildPhase', () { testWithoutContext('fails if missing PBXFrameworksBuildPhase section', () async { final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); @@ -1045,7 +1195,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1093,7 +1242,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1146,7 +1294,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1191,7 +1338,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1242,7 +1388,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1293,7 +1438,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1351,7 +1495,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1369,6 +1512,294 @@ void main() { }); }); + group('migrate PBXGroup', () { + testWithoutContext('fails if missing PBXGroup section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_groupSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_groupSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXGroup section'), + ); + }); + + testWithoutContext('fails if missing Flutter group in parsed settings', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_groupSectionIndex] = unmigratedGroupSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_groupSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find parsed Flutter PBXGroup.'), + ); + }); + + testWithoutContext( + 'fails if missing Flutter group subsection following PBXGroup begin header', + () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_groupSectionIndex] = ''' +/* Begin PBXGroup section */ +/* End PBXGroup section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find Flutter PBXGroup.'), + ); + }, + ); + + testWithoutContext( + 'fails if missing Flutter group subsection before PBXGroup end header', + () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_groupSectionIndex] = ''' +/* Begin PBXGroup section */ +/* End PBXGroup section */ +/* Begin NonExistant section */ + ${_flutterGroupIdentifier(platform)} /* Flutter */ = { + }; +/* End NonExistant section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find Flutter PBXGroup.'), + ); + }, + ); + + testWithoutContext('successfully added when children field is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_groupSectionIndex] = unmigratedGroupSection( + platform, + missingChildren: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_groupSectionIndex] = unmigratedGroupSectionAsJson( + platform, + missingChildren: true, + ); + final List expectedSettings = [..._allSectionsMigrated(platform)]; + expectedSettings[_groupSectionIndex] = migratedGroupSection( + platform, + missingChildren: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXGroup already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when children field is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_groupSectionIndex] = unmigratedGroupSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [..._allSectionsMigrated(platform)]; + expectedSettings[_groupSectionIndex] = migratedGroupSection(platform); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXGroup already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + group('migrate PBXNativeTarget', () { testWithoutContext('fails if missing PBXNativeTarget section', () async { final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); @@ -1403,7 +1834,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1446,7 +1876,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1491,7 +1920,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1541,7 +1969,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1600,7 +2027,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1655,7 +2081,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1716,7 +2141,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -1767,7 +2191,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1814,7 +2237,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1866,7 +2288,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1908,7 +2329,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -1965,7 +2385,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2018,7 +2437,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2079,7 +2497,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2123,7 +2540,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -2169,7 +2585,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2222,7 +2637,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2278,7 +2692,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2323,7 +2736,6 @@ void main() { plistParser: FakePlistParser( json: _plutilOutput(settingsAsJsonBeforeMigration), ), - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -2366,7 +2778,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2419,7 +2830,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2475,7 +2885,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await projectMigration.migrate(); expect(testLogger.errorText, isEmpty); @@ -2493,6 +2902,100 @@ void main() { }); }); + testWithoutContext('migrates only PBXFileReference and PBXGroup', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsMigrated(platform), + ]; + settingsBeforeMigration[_fileReferenceSectionIndex] = unmigratedFileReferenceSection; + settingsBeforeMigration[_groupSectionIndex] = unmigratedGroupSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_fileReferenceSectionIndex] = + unmigratedFileReferenceAsJson; + settingsAsJsonBeforeMigration[_groupSectionIndex] = unmigratedGroupSectionAsJson( + platform, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = + SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXBuildFile already migrated. Skipping...'), + isTrue, + ); + expect( + testLogger.traceText.contains('PBXFileReference already migrated. Skipping...'), + isFalse, + ); + expect( + testLogger.traceText.contains( + 'PBXFrameworksBuildPhase already migrated. Skipping...', + ), + isTrue, + ); + expect( + testLogger.traceText.contains('PBXGroup already migrated. Skipping...'), + isFalse, + ); + expect( + testLogger.traceText.contains('PBXNativeTarget already migrated. Skipping...'), + isTrue, + ); + expect( + testLogger.traceText.contains('PBXProject already migrated. Skipping...'), + isTrue, + ); + expect( + testLogger.traceText.contains( + 'XCLocalSwiftPackageReference already migrated. Skipping...', + ), + isTrue, + ); + expect( + testLogger.traceText.contains( + 'XCSwiftPackageProductDependency already migrated. Skipping...', + ), + isTrue, + ); + settingsBeforeMigration[_fileReferenceSectionIndex] = migratedFileReferenceSection( + platform, + ); + settingsBeforeMigration[_groupSectionIndex] = migratedGroupSection(platform); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(settingsBeforeMigration), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + testWithoutContext('throw if settings not updated correctly', () async { final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); final BufferLogger testLogger = BufferLogger.test(); @@ -2520,7 +3023,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -2530,10 +3032,20 @@ void main() { testLogger.errorText, contains('PBXBuildFile was not migrated or was migrated incorrectly.'), ); + expect( + testLogger.errorText, + contains( + 'PBXFileReference for FlutterGeneratedPluginSwiftPackage was not migrated or was migrated incorrectly.', + ), + ); expect( testLogger.errorText, contains('PBXFrameworksBuildPhase was not migrated or was migrated incorrectly.'), ); + expect( + testLogger.errorText, + contains('PBXGroup was not migrated or was migrated incorrectly.'), + ); expect( testLogger.errorText, contains('PBXNativeTarget was not migrated or was migrated incorrectly.'), @@ -2588,7 +3100,6 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, ); await expectLater( () => projectMigration.migrate(), @@ -2625,7 +3136,7 @@ void main() { logger: testLogger, fileSystem: memoryFileSystem, plistParser: plistParser, - features: swiftPackageManagerFullyEnabledFlags, + validateBackup: true, ); await expectLater(() async => projectMigration.migrate(), throwsToolExit()); @@ -2739,16 +3250,20 @@ String _validBuildableReference(SupportedPlatform platform) { } const int _buildFileSectionIndex = 0; -const int _frameworksBuildPhaseSectionIndex = 1; -const int _nativeTargetSectionIndex = 2; -const int _projectSectionIndex = 3; -const int _localSwiftPackageReferenceSectionIndex = 4; -const int _swiftPackageProductDependencySectionIndex = 5; +const int _fileReferenceSectionIndex = 1; +const int _frameworksBuildPhaseSectionIndex = 2; +const int _groupSectionIndex = 3; +const int _nativeTargetSectionIndex = 4; +const int _projectSectionIndex = 5; +const int _localSwiftPackageReferenceSectionIndex = 6; +const int _swiftPackageProductDependencySectionIndex = 7; List _allSectionsMigrated(SupportedPlatform platform) { return [ migratedBuildFileSection, + migratedFileReferenceSection(platform), migratedFrameworksBuildPhaseSection(platform), + migratedGroupSection(platform), migratedNativeTargetSection(platform), migratedProjectSection(platform), migratedLocalSwiftPackageReferenceSection(), @@ -2759,7 +3274,9 @@ List _allSectionsMigrated(SupportedPlatform platform) { List _allSectionsMigratedAsJson(SupportedPlatform platform) { return [ migratedBuildFileSectionAsJson, + migratedFileReferenceAsJson(platform), migratedFrameworksBuildPhaseSectionAsJson(platform), + migratedGroupSectionAsJson(platform), migratedNativeTargetSectionAsJson(platform), migratedProjectSectionAsJson(platform), migratedLocalSwiftPackageReferenceSectionAsJson, @@ -2770,7 +3287,9 @@ List _allSectionsMigratedAsJson(SupportedPlatform platform) { List _allSectionsUnmigrated(SupportedPlatform platform) { return [ unmigratedBuildFileSection, + unmigratedFileReferenceSection, unmigratedFrameworksBuildPhaseSection(platform), + unmigratedGroupSection(platform), unmigratedNativeTargetSection(platform), unmigratedProjectSection(platform), unmigratedLocalSwiftPackageReferenceSection(), @@ -2781,7 +3300,9 @@ List _allSectionsUnmigrated(SupportedPlatform platform) { List _allSectionsUnmigratedAsJson(SupportedPlatform platform) { return [ unmigratedBuildFileSectionAsJson, + unmigratedFileReferenceAsJson, unmigratedFrameworksBuildPhaseSectionAsJson(platform), + unmigratedGroupSectionAsJson(platform), unmigratedNativeTargetSectionAsJson(platform), unmigratedProjectSectionAsJson(platform), ]; @@ -2813,6 +3334,12 @@ String _runnerFrameworksBuildPhaseIdentifier(SupportedPlatform platform) { : '33CC10EA2044A3C60003C045'; } +String _flutterGroupIdentifier(SupportedPlatform platform) { + return platform == SupportedPlatform.ios + ? '9740EEB11CF90186004384FC' + : '33CEB47122A05771004F2AC0'; +} + String _runnerNativeTargetIdentifier(SupportedPlatform platform) { return platform == SupportedPlatform.ios ? '97C146ED1CF9000F007C117D' @@ -2825,6 +3352,10 @@ String _projectIdentifier(SupportedPlatform platform) { : '33CC10E52044A3C60003C045'; } +String _relativeEphemeralPath(SupportedPlatform platform) { + return platform == SupportedPlatform.ios ? 'Flutter/ephemeral' : 'ephemeral'; +} + // PBXBuildFile const String unmigratedBuildFileSection = ''' /* Begin PBXBuildFile section */ @@ -2862,6 +3393,61 @@ const String migratedBuildFileSectionAsJson = ''' "isa" : "PBXBuildFile" }'''; +// PBXFileReference +const String unmigratedFileReferenceSection = ''' +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; +/* End PBXFileReference section */ +'''; +String migratedFileReferenceSection(SupportedPlatform platform) { + return ''' +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ${_relativeEphemeralPath(platform)}/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; +/* End PBXFileReference section */ +'''; +} + +const String unmigratedFileReferenceAsJson = ''' + "1498D2321E8E86230040F4C2": { + "path": "GeneratedPluginRegistrant.h", + "isa": "PBXFileReference", + "lastKnownFileType": "sourcecode.c.h", + "sourceTree": "" + }, + "1498D2331E8E89220040F4C2": { + "path": "GeneratedPluginRegistrant.m", + "isa": "PBXFileReference", + "lastKnownFileType": "sourcecode.c.objc", + "sourceTree": "", + "fileEncoding": "4" + }'''; +String migratedFileReferenceAsJson(SupportedPlatform platform) { + return ''' + "1498D2321E8E86230040F4C2": { + "path": "GeneratedPluginRegistrant.h", + "isa": "PBXFileReference", + "lastKnownFileType": "sourcecode.c.h", + "sourceTree": "" + }, + "1498D2331E8E89220040F4C2": { + "path": "GeneratedPluginRegistrant.m", + "isa": "PBXFileReference", + "lastKnownFileType": "sourcecode.c.objc", + "sourceTree": "", + "fileEncoding": "4" + }, + "78E0A7A72DC9AD7400C4905E": { + "path": "${_relativeEphemeralPath(platform)}/Packages/FlutterGeneratedPluginSwiftPackage", + "isa": "PBXFileReference", + "name": "flutter", + "lastKnownFileType": "wrapper", + "sourceTree": "" + }'''; +} + // PBXFrameworksBuildPhase String unmigratedFrameworksBuildPhaseSection( SupportedPlatform platform, { @@ -2939,6 +3525,90 @@ String migratedFrameworksBuildPhaseSectionAsJson(SupportedPlatform platform) { }'''; } +// PBXGroup +String unmigratedGroupSection(SupportedPlatform platform, {bool missingChildren = false}) { + return [ + '/* Begin PBXGroup section */', + ' ${_flutterGroupIdentifier(platform)} /* Flutter */ = {', + ' isa = PBXGroup;', + if (!missingChildren) ...[ + ' children = (', + ' 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,', + ' 9740EEB21CF90195004384FC /* Debug.xcconfig */,', + ' 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,', + ' 9740EEB31CF90195004384FC /* Generated.xcconfig */,', + ' );', + ], + ' name = Flutter;', + ' sourceTree = "";', + ' };', + '/* End PBXGroup section */', + ].join('\n'); +} + +String migratedGroupSection(SupportedPlatform platform, {bool missingChildren = false}) { + return [ + '/* Begin PBXGroup section */', + ' ${_flutterGroupIdentifier(platform)} /* Flutter */ = {', + if (missingChildren) ...[ + ' children = (', + ' 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,', + ' );', + ' isa = PBXGroup;', + ] else ...[ + ' isa = PBXGroup;', + ' children = (', + ' 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,', + ' 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,', + ' 9740EEB21CF90195004384FC /* Debug.xcconfig */,', + ' 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,', + ' 9740EEB31CF90195004384FC /* Generated.xcconfig */,', + ' );', + ], + ' name = Flutter;', + ' sourceTree = "";', + ' };', + '/* End PBXGroup section */', + ].join('\n'); +} + +String unmigratedGroupSectionAsJson(SupportedPlatform platform, {bool missingChildren = false}) { + return [ + ' "${_flutterGroupIdentifier(platform)}" : {', + ' "isa": "PBXGroup",', + ' "name": "Flutter",', + if (!missingChildren) ...[ + ' "children": [', + ' "3B3967151E833CAA004F5970",', + ' "9740EEB21CF90195004384FC",', + ' "7AFA3C8E1D35360C0083082E",', + ' "9740EEB31CF90195004384FC"', + ' ],', + ], + ' "sourceTree": ""', + ' }', + ].join('\n'); +} + +String migratedGroupSectionAsJson(SupportedPlatform platform, {bool missingChildren = false}) { + return [ + ' "${_flutterGroupIdentifier(platform)}" : {', + ' "isa": "PBXGroup",', + ' "name": "Flutter",', + ' "children": [', + if (missingChildren) ...[' "78E0A7A72DC9AD7400C4905E",'] else ...[ + ' "78E0A7A72DC9AD7400C4905E",', + ' "3B3967151E833CAA004F5970",', + ' "9740EEB21CF90195004384FC",', + ' "7AFA3C8E1D35360C0083082E",', + ' "9740EEB31CF90195004384FC"', + ], + ' ],', + ' "sourceTree": ""', + ' }', + ].join('\n'); +} + // PBXNativeTarget String unmigratedNativeTargetSection( SupportedPlatform platform, { @@ -3385,6 +4055,7 @@ class FakeXcodeProject extends Fake implements IosProject { required MemoryFileSystem fileSystem, required String platform, required this.logger, + this.usesSwiftPackageManager = true, }) : hostAppRoot = fileSystem.directory('app_name').childDirectory(platform), parent = FakeFlutterProject(fileSystem: fileSystem); @@ -3414,6 +4085,9 @@ class FakeXcodeProject extends Fake implements IosProject { @override String hostAppProjectName = 'Runner'; + @override + bool usesSwiftPackageManager; + @override Directory get flutterPluginSwiftPackageDirectory => hostAppRoot .childDirectory('Flutter') @@ -3463,7 +4137,6 @@ class FakeSwiftPackageManagerIntegrationMigration extends SwiftPackageManagerInt required super.logger, required super.fileSystem, required super.plistParser, - required super.features, this.validateBackup = false, }) : _xcodeProject = project;