From a0814376b7789fab39bdc3b876a4c8393eb1b225 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 Aug 2025 11:58:32 +0100 Subject: [PATCH 1/5] ci(apple): fix CI runners with explicit Xcode version --- .github/workflows/all_plugins.yaml | 2 ++ .github/workflows/e2e_tests_fdc.yaml | 2 ++ .github/workflows/ios.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/all_plugins.yaml b/.github/workflows/all_plugins.yaml index 09093cb9583a..89e0d0d7430d 100644 --- a/.github/workflows/all_plugins.yaml +++ b/.github/workflows/all_plugins.yaml @@ -133,6 +133,8 @@ jobs: - uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff with: channel: 'stable' + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup firebase_core example app to test Swift integration # run this before running melos boostrap to ensure the example app is set up run: | diff --git a/.github/workflows/e2e_tests_fdc.yaml b/.github/workflows/e2e_tests_fdc.yaml index ca155dba8408..4e83c5cb096a 100644 --- a/.github/workflows/e2e_tests_fdc.yaml +++ b/.github/workflows/e2e_tests_fdc.yaml @@ -100,6 +100,8 @@ jobs: name: Install Node.js 20 with: node-version: '20' + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b with: distribution: 'temurin' diff --git a/.github/workflows/ios.yaml b/.github/workflows/ios.yaml index 80d627a7e827..28865c50d745 100644 --- a/.github/workflows/ios.yaml +++ b/.github/workflows/ios.yaml @@ -39,6 +39,8 @@ jobs: name: Install Node.js 20 with: node-version: '20' + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b with: distribution: 'temurin' From 60c49237be747e80707c2420474047acb311c8a4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 Aug 2025 13:13:41 +0100 Subject: [PATCH 2/5] chore: format script --- .../workflows/scripts/swift-integration.dart | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/scripts/swift-integration.dart b/.github/workflows/scripts/swift-integration.dart index 9cabd662d971..9294422e8af4 100644 --- a/.github/workflows/scripts/swift-integration.dart +++ b/.github/workflows/scripts/swift-integration.dart @@ -32,7 +32,9 @@ Future getCurrentBranch() async { if (branch == null || branch.isEmpty) { // Fallback to git command for local testing - print('GitHub Actions environment variables not found, trying git command...'); + print( + 'GitHub Actions environment variables not found, trying git command...', + ); final result = await Process.run('git', ['branch', '--show-current']); if (result.exitCode != 0) { throw Exception('Failed to get current git branch: ${result.stderr}'); @@ -41,13 +43,18 @@ Future getCurrentBranch() async { } if (branch.isEmpty) { - throw Exception('Could not determine current branch from GitHub Actions environment or git command'); + throw Exception( + 'Could not determine current branch from GitHub Actions environment or git command', + ); } return branch; } -Future updatePackageSwiftFiles(String branch, List packages) async { +Future updatePackageSwiftFiles( + String branch, + List packages, +) async { print('Updating Package.swift files to use branch: $branch'); // Update each package's Package.swift files @@ -56,12 +63,16 @@ Future updatePackageSwiftFiles(String branch, List packages) async } } -Future updatePackageSwiftForPackage(String packageName, String branch) async { +Future updatePackageSwiftForPackage( + String packageName, + String branch, +) async { // Check both ios and macos directories final platforms = ['ios', 'macos']; for (final platform in platforms) { - final packageSwiftPath = 'packages/$packageName/$packageName/$platform/$packageName/Package.swift'; + final packageSwiftPath = + 'packages/$packageName/$packageName/$platform/$packageName/Package.swift'; final file = File(packageSwiftPath); if (!file.existsSync()) { @@ -78,7 +89,7 @@ Future updatePackageSwiftForPackage(String packageName, String branch) asy // Pattern to match the exact version dependency final exactVersionPattern = RegExp( r'\.package\(url: "https://github\.com/firebase/flutterfire", exact: [^)]+\)', - multiLine: true + multiLine: true, ); final headRepo = Platform.environment['PR_HEAD_REPO']; @@ -88,10 +99,14 @@ Future updatePackageSwiftForPackage(String packageName, String branch) asy final repoSlug = headRepo != baseRepo ? headRepo : baseRepo; // Replace with branch dependency - final branchDependency = '.package(url: "https://github.com/$repoSlug", branch: "$branch")'; + final branchDependency = + '.package(url: "https://github.com/$repoSlug", branch: "$branch")'; if (exactVersionPattern.hasMatch(content)) { - updatedContent = content.replaceAll(exactVersionPattern, branchDependency); + updatedContent = content.replaceAll( + exactVersionPattern, + branchDependency, + ); await file.writeAsString(updatedContent); print('✓ Updated $packageSwiftPath to use branch: $branch'); } else { @@ -106,8 +121,9 @@ Future buildSwiftExampleApp(String platform, String plugins) async { print('Building example app with swift (SPM) integration for $plugins'); - final directory = - Directory('packages/firebase_core/firebase_core/example/$platform'); + final directory = Directory( + 'packages/firebase_core/firebase_core/example/$platform', + ); if (!directory.existsSync()) { print('Directory does not exist: ${directory.path}'); exit(1); @@ -140,7 +156,8 @@ Future buildSwiftExampleApp(String platform, String plugins) async { exit(1); } else { print( - 'Successfully built $plugins for $platformName project using Swift Package Manager.'); + 'Successfully built $plugins for $platformName project using Swift Package Manager.', + ); Directory.current = Directory('..'); print('See contents of pubspec.yaml:'); @@ -151,7 +168,9 @@ Future buildSwiftExampleApp(String platform, String plugins) async { } Future _runCommand( - String command, List arguments) async { + String command, + List arguments, +) async { final process = await Process.start(command, arguments); // Listen to stdout From 5f1fce245835433f3251ca5fd4eabfe4f595e567 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 Aug 2025 14:25:47 +0100 Subject: [PATCH 3/5] chore: debug logs for script --- .../workflows/scripts/swift-integration.dart | 287 +++++++++++++++++- 1 file changed, 285 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/swift-integration.dart b/.github/workflows/scripts/swift-integration.dart index 9294422e8af4..1de5e26a2148 100644 --- a/.github/workflows/scripts/swift-integration.dart +++ b/.github/workflows/scripts/swift-integration.dart @@ -5,7 +5,15 @@ import 'dart:io'; import 'dart:convert'; +final debugMode = true; + void main(List arguments) async { + if (debugMode) { + print('[DEBUG] main: Starting swift-integration script'); + print('[DEBUG] Arguments: ${arguments.join(', ')}'); + print('[DEBUG] Number of arguments: ${arguments.length}'); + } + if (arguments.isEmpty) { throw Exception('No FlutterFire dependency arguments provided.'); } @@ -14,20 +22,64 @@ void main(List arguments) async { final currentBranch = await getCurrentBranch(); print('Current branch: $currentBranch'); + if (debugMode) { + print( + '[DEBUG] About to update Package.swift files for branch: $currentBranch', + ); + } + // Update all Package.swift files to use branch dependencies await updatePackageSwiftFiles(currentBranch, arguments); + if (debugMode) { + print('[DEBUG] Package.swift files updated, starting builds'); + } + final plugins = arguments.join(','); + + if (debugMode) { + print('[DEBUG] Building iOS first...'); + } await buildSwiftExampleApp('ios', plugins); + + if (debugMode) { + print('[DEBUG] iOS build completed, now building macOS...'); + } await buildSwiftExampleApp('macos', plugins); + + if (debugMode) { + print('[DEBUG] main: All builds completed successfully'); + } } Future getCurrentBranch() async { + if (debugMode) { + print('[DEBUG] getCurrentBranch: Starting branch detection'); + print('[DEBUG] Environment variables:'); + print( + '[DEBUG] GITHUB_HEAD_REF: ${Platform.environment['GITHUB_HEAD_REF']}', + ); + print( + '[DEBUG] GITHUB_REF_NAME: ${Platform.environment['GITHUB_REF_NAME']}', + ); + print( + '[DEBUG] GITHUB_REPOSITORY: ${Platform.environment['GITHUB_REPOSITORY']}', + ); + print('[DEBUG] PR_HEAD_REPO: ${Platform.environment['PR_HEAD_REPO']}'); + } + // Try GitHub Actions environment variables first String? branch = Platform.environment['GITHUB_HEAD_REF']; // For pull requests + if (debugMode && branch != null) { + print('[DEBUG] Found branch from GITHUB_HEAD_REF: $branch'); + } + if (branch == null || branch.isEmpty) { branch = Platform.environment['GITHUB_REF_NAME']; // For direct pushes + if (debugMode && branch != null) { + print('[DEBUG] Found branch from GITHUB_REF_NAME: $branch'); + } } if (branch == null || branch.isEmpty) { @@ -35,19 +87,35 @@ Future getCurrentBranch() async { print( 'GitHub Actions environment variables not found, trying git command...', ); + if (debugMode) { + print('[DEBUG] Executing: git branch --show-current'); + } final result = await Process.run('git', ['branch', '--show-current']); if (result.exitCode != 0) { + if (debugMode) { + print('[DEBUG] Git command failed with exit code: ${result.exitCode}'); + print('[DEBUG] Git stderr: ${result.stderr}'); + } throw Exception('Failed to get current git branch: ${result.stderr}'); } branch = result.stdout.toString().trim(); + if (debugMode) { + print('[DEBUG] Found branch from git command: $branch'); + } } if (branch.isEmpty) { + if (debugMode) { + print('[DEBUG] No branch found from any method'); + } throw Exception( 'Could not determine current branch from GitHub Actions environment or git command', ); } + if (debugMode) { + print('[DEBUG] Final branch: $branch'); + } return branch; } @@ -57,24 +125,53 @@ Future updatePackageSwiftFiles( ) async { print('Updating Package.swift files to use branch: $branch'); + if (debugMode) { + print( + '[DEBUG] updatePackageSwiftFiles: Processing ${packages.length} packages', + ); + print('[DEBUG] Packages: ${packages.join(', ')}'); + } + // Update each package's Package.swift files for (final package in packages) { + if (debugMode) { + print('[DEBUG] Processing package: $package'); + } await updatePackageSwiftForPackage(package, branch); } + + if (debugMode) { + print('[DEBUG] updatePackageSwiftFiles: Completed processing all packages'); + } } Future updatePackageSwiftForPackage( String packageName, String branch, ) async { + if (debugMode) { + print( + '[DEBUG] updatePackageSwiftForPackage: Starting for package $packageName', + ); + } + // Check both ios and macos directories final platforms = ['ios', 'macos']; + if (debugMode) { + print('[DEBUG] Will check platforms: ${platforms.join(', ')}'); + } + for (final platform in platforms) { final packageSwiftPath = 'packages/$packageName/$packageName/$platform/$packageName/Package.swift'; final file = File(packageSwiftPath); + if (debugMode) { + print('[DEBUG] Checking path: $packageSwiftPath'); + print('[DEBUG] File exists: ${file.existsSync()}'); + } + if (!file.existsSync()) { print('Warning: Package.swift not found at $packageSwiftPath'); continue; @@ -83,6 +180,14 @@ Future updatePackageSwiftForPackage( print('Updating $packageSwiftPath'); final content = await file.readAsString(); + if (debugMode) { + print('[DEBUG] File content length: ${content.length} characters'); + print('[DEBUG] Content preview (first 200 chars):'); + print( + '[DEBUG] ${content.length > 200 ? content.substring(0, 200) : content}...', + ); + } + // Replace exact version dependency with branch dependency String updatedContent = content; @@ -92,94 +197,264 @@ Future updatePackageSwiftForPackage( multiLine: true, ); + if (debugMode) { + final matches = exactVersionPattern.allMatches(content); + print('[DEBUG] Regex pattern matches found: ${matches.length}'); + for (final match in matches) { + print('[DEBUG] Match: ${match.group(0)}'); + } + } + final headRepo = Platform.environment['PR_HEAD_REPO']; final baseRepo = Platform.environment['GITHUB_REPOSITORY']; + if (debugMode) { + print('[DEBUG] PR_HEAD_REPO: $headRepo'); + print('[DEBUG] GITHUB_REPOSITORY: $baseRepo'); + } + // handles forked repositories final repoSlug = headRepo != baseRepo ? headRepo : baseRepo; + print('repoSlug: $repoSlug'); + print('branch: $branch'); + + if (debugMode) { + print( + '[DEBUG] Using repoSlug: $repoSlug (headRepo != baseRepo: ${headRepo != baseRepo})', + ); + } // Replace with branch dependency final branchDependency = '.package(url: "https://github.com/$repoSlug", branch: "$branch")'; + if (debugMode) { + print('[DEBUG] Branch dependency string: $branchDependency'); + } + if (exactVersionPattern.hasMatch(content)) { updatedContent = content.replaceAll( exactVersionPattern, branchDependency, ); + + if (debugMode) { + print('[DEBUG] Content was modified, writing to file'); + print('[DEBUG] Updated content preview (first 200 chars):'); + print( + '[DEBUG] ${updatedContent.length > 200 ? updatedContent.substring(0, 200) : updatedContent}...', + ); + } + await file.writeAsString(updatedContent); print('✓ Updated $packageSwiftPath to use branch: $branch'); } else { print('⚠ No exact version dependency found in $packageSwiftPath'); + if (debugMode) { + print('[DEBUG] Content did not match regex pattern'); + print('[DEBUG] Looking for pattern: ${exactVersionPattern.pattern}'); + } } } + + if (debugMode) { + print( + '[DEBUG] updatePackageSwiftForPackage: Completed for package $packageName', + ); + } } Future buildSwiftExampleApp(String platform, String plugins) async { final initialDirectory = Directory.current; final platformName = platform == 'ios' ? 'iOS' : 'macOS'; + if (debugMode) { + print('[DEBUG] buildSwiftExampleApp: Starting build for $platformName'); + print('[DEBUG] Initial directory: ${initialDirectory.path}'); + print('[DEBUG] Platform: $platform'); + print('[DEBUG] Plugins: $plugins'); + } + print('Building example app with swift (SPM) integration for $plugins'); final directory = Directory( 'packages/firebase_core/firebase_core/example/$platform', ); + + if (debugMode) { + print('[DEBUG] Target directory: ${directory.path}'); + print('[DEBUG] Directory exists: ${directory.existsSync()}'); + } + if (!directory.existsSync()) { print('Directory does not exist: ${directory.path}'); exit(1); } // Change to the appropriate directory + if (debugMode) { + print( + '[DEBUG] Changing directory from ${Directory.current.path} to ${directory.path}', + ); + } Directory.current = directory; + if (debugMode) { + print('[DEBUG] Current directory after change: ${Directory.current.path}'); + print('[DEBUG] Listing current directory contents:'); + try { + final contents = Directory.current.listSync(); + for (final item in contents) { + print('[DEBUG] ${item.path.split('/').last}'); + } + } catch (e) { + print('[DEBUG] Error listing directory: $e'); + } + } + await _runCommand('rm', ['Podfile']); await _runCommand('pod', ['deintegrate']); + // Check what SPM packages are being resolved before build + if (debugMode) { + print('[DEBUG] Checking for existing SPM packages directory...'); + final spmPackagesDir = Directory('$platform/Flutter/ephemeral/Packages'); + if (spmPackagesDir.existsSync()) { + print('[DEBUG] SPM Packages directory exists: ${spmPackagesDir.path}'); + try { + final packages = spmPackagesDir.listSync(recursive: true); + for (final package in packages) { + if (package is Directory) { + print('[DEBUG] SPM Package directory: ${package.path}'); + } + } + } catch (e) { + print('[DEBUG] Error listing SPM packages: $e'); + } + } else { + print('[DEBUG] SPM Packages directory does not exist yet'); + } + } + // Determine the arguments for the flutter build command final flutterArgs = ['build', platform]; if (platform == 'ios') { flutterArgs.add('--no-codesign'); } + if (debugMode) { + print('[DEBUG] Flutter command args: ${flutterArgs.join(' ')}'); + } + // Run the flutter build command final flutterResult = await _runCommand('flutter', flutterArgs); + // Check what SPM packages were resolved after build + if (debugMode && flutterResult.exitCode != 0) { + print('[DEBUG] Build failed, checking SPM packages directory for clues...'); + final spmPackagesDir = Directory('$platform/Flutter/ephemeral/Packages'); + if (spmPackagesDir.existsSync()) { + print( + '[DEBUG] SPM Packages directory after failed build: ${spmPackagesDir.path}', + ); + try { + final packages = spmPackagesDir.listSync(recursive: true); + for (final package in packages) { + if (package is Directory && + package.path.contains('firebase_messaging')) { + print('[DEBUG] Found firebase_messaging package: ${package.path}'); + // Check if it's trying to access iOS resources from macOS build + final resourcesDir = Directory( + '${package.path}/Sources/firebase_messaging/Resources', + ); + if (resourcesDir.existsSync()) { + print('[DEBUG] Resources directory exists: ${resourcesDir.path}'); + final resources = resourcesDir.listSync(); + for (final resource in resources) { + print('[DEBUG] Resource: ${resource.path}'); + } + } else { + print( + '[DEBUG] Resources directory does not exist: ${resourcesDir.path}', + ); + } + } + } + } catch (e) { + print('[DEBUG] Error listing SPM packages after build: $e'); + } + } + } + + if (debugMode) { + print('[DEBUG] Flutter build exit code: ${flutterResult.exitCode}'); + } + // Check if the flutter build command was successful if (flutterResult.exitCode != 0) { print('Flutter build failed with exit code ${flutterResult.exitCode}.'); + if (debugMode) { + print('[DEBUG] Flutter build failed, exiting'); + } exit(1); } // Check the output for the specific string if (flutterResult.stdout.contains('Running pod install')) { print('Failed. Pods are being installed when they should not be.'); + if (debugMode) { + print( + '[DEBUG] Found "Running pod install" in output, this should not happen with SPM', + ); + } exit(1); } else { print( 'Successfully built $plugins for $platformName project using Swift Package Manager.', ); + if (debugMode) { + print('[DEBUG] Build successful, changing to parent directory'); + } Directory.current = Directory('..'); print('See contents of pubspec.yaml:'); await _runCommand('cat', ['pubspec.yaml']); } + if (debugMode) { + print('[DEBUG] Restoring original directory: ${initialDirectory.path}'); + } Directory.current = initialDirectory; + + if (debugMode) { + print('[DEBUG] buildSwiftExampleApp: Completed build for $platformName'); + } } Future _runCommand( String command, List arguments, ) async { + if (debugMode) { + print( + '[DEBUG] _runCommand: Executing command: $command ${arguments.join(' ')}', + ); + print('[DEBUG] Current working directory: ${Directory.current.path}'); + } + final process = await Process.start(command, arguments); + final stdoutBuffer = StringBuffer(); + final stderrBuffer = StringBuffer(); + // Listen to stdout process.stdout.transform(utf8.decoder).listen((data) { - print(data); + stdoutBuffer.write(data); }); // Listen to stderr process.stderr.transform(utf8.decoder).listen((data) { + stderrBuffer.write(data); print('stderr output: $data'); }); @@ -188,7 +463,15 @@ Future _runCommand( if (exitCode != 0) { print('Command failed: $command ${arguments.join(' ')}'); + if (debugMode) { + print('[DEBUG] Command failed with exit code $exitCode'); + } } - return ProcessResult(process.pid, exitCode, '', ''); + return ProcessResult( + process.pid, + exitCode, + stdoutBuffer.toString(), + stderrBuffer.toString(), + ); } From 938818712bde90cac3cbfd1a66860942ec2810a1 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 18 Aug 2025 14:38:35 +0100 Subject: [PATCH 4/5] chore: clean ios build --- .github/workflows/scripts/swift-integration.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/scripts/swift-integration.dart b/.github/workflows/scripts/swift-integration.dart index 1de5e26a2148..cf60f53391bb 100644 --- a/.github/workflows/scripts/swift-integration.dart +++ b/.github/workflows/scripts/swift-integration.dart @@ -346,6 +346,10 @@ Future buildSwiftExampleApp(String platform, String plugins) async { print('[DEBUG] Flutter command args: ${flutterArgs.join(' ')}'); } + if (platform == 'macos') { + await _runCommand('flutter', ['clean']); + } + // Run the flutter build command final flutterResult = await _runCommand('flutter', flutterArgs); @@ -440,7 +444,7 @@ Future _runCommand( '[DEBUG] _runCommand: Executing command: $command ${arguments.join(' ')}', ); print('[DEBUG] Current working directory: ${Directory.current.path}'); - } + } final process = await Process.start(command, arguments); From 3b786355552cebdb3fe428286e962a61f839afe0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 19 Aug 2025 08:35:59 +0100 Subject: [PATCH 5/5] ci: temp remove of messaging from script --- .github/workflows/scripts/swift-integration.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/swift-integration.dart b/.github/workflows/scripts/swift-integration.dart index cf60f53391bb..e64390df2bf4 100644 --- a/.github/workflows/scripts/swift-integration.dart +++ b/.github/workflows/scripts/swift-integration.dart @@ -5,7 +5,7 @@ import 'dart:io'; import 'dart:convert'; -final debugMode = true; +final debugMode = false; void main(List arguments) async { if (debugMode) { @@ -123,9 +123,9 @@ Future updatePackageSwiftFiles( String branch, List packages, ) async { - print('Updating Package.swift files to use branch: $branch'); - if (debugMode) { + print('[DEBUG] updatePackageSwiftFiles: Starting'); + print('[DEBUG] Branch: $branch'); print( '[DEBUG] updatePackageSwiftFiles: Processing ${packages.length} packages', ); @@ -347,6 +347,9 @@ Future buildSwiftExampleApp(String platform, String plugins) async { } if (platform == 'macos') { + // TODO: temp solution to macos to remove firebase_messaging from build + // See: https://github.com/firebase/flutterfire/actions/runs/17042278122/job/48308815666?pr=17634#step:8:787 + await _runCommand('flutter', ['pub', 'remove', 'firebase_messaging']); await _runCommand('flutter', ['clean']); }