From 0e0f88b6b7531bd598642a22e9263116d896147d Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Wed, 13 May 2026 13:29:54 -0500 Subject: [PATCH 1/5] Move prefetchSwiftPackages to be per platform --- .../flutter_tools/lib/src/ios/xcodeproj.dart | 117 +---- .../flutter_tools/lib/src/xcode_project.dart | 110 +++++ .../general.shard/ios/xcodeproj_test.dart | 443 +---------------- .../general.shard/xcode_project_test.dart | 453 ++++++++++++++++++ 4 files changed, 586 insertions(+), 537 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 003efeb68816a..812edaed93e54 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -20,7 +20,6 @@ import '../base/terminal.dart'; import '../base/utils.dart'; import '../base/version.dart'; import '../build_info.dart'; -import '../convert.dart'; import '../reporting/reporting.dart'; import '../xcode_project.dart'; @@ -387,117 +386,25 @@ class XcodeProjectInterpreter { ], workingDirectory: projectPath); } - /// The process used to fetch Swift packages. - Process? _swiftPackageFetchProcess; - - /// The stdout subscription for the Swift package fetch process. - StreamSubscription? _swiftPackageFetchStdoutSubscription; - - /// The stderr subscription for the Swift package fetch process. - StreamSubscription? _swiftPackageFetchStderrSubscription; - /// Prefetches Swift packages for the given Xcode project. - /// - /// If a process is already running from a previous Flutter command, kill it before starting - /// the command. If the process is already running from the same Flutter command, wait for it to - /// complete if [waitForCompletion] is true. - /// - /// If [quiet] is false, it will print a spinner while the command is running and print logs of - /// what Swift packages are being fetched. Future prefetchSwiftPackages( XcodeBasedProject xcodeProject, { required Directory buildDirectory, bool quiet = true, bool waitForCompletion = true, }) async { - final String projectPath = xcodeProject.hostAppRoot.path; - Status? status; - try { - final command = [ - ..._xcodebuildProjectCommandArguments( - buildDirectory, - // skipPackageUpdatesAndValidation should be false so that when subsequent xcodebuild - // commands run, packages should already be resolved, downloaded, updated, and validated. - skipPackageUpdatesAndValidation: false, - ), - '-resolvePackageDependencies', - ]; - if (_swiftPackageFetchProcess == null) { - // Remove the `xcrun` prefixes from the command before comparing because the process name - // will resolve to the actual xcodebuild path, such as this: - // /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild - final int xcodebuildIndex = command.indexOf('xcodebuild'); - if (xcodebuildIndex == -1) { - // This should never happen. The _xcodebuildProjectCommandArguments always includes - // xcodebuild. - throw StateError('Command "${command.join(' ')}" is expected to contain `xcodebuild`.'); - } - final String commandToMatch = command.sublist(xcodebuildIndex).join(' '); - - // Check if process is already running from a previous Flutter command. If it is, kill it - // so we don't have the process running twice. When this process is run twice, it'll cause - // one to error. The new process will pick up where the old one left off. - final RunResult result = await _processUtils.run([ - 'pgrep', - '-n', // Select only the newest - '-f', // Match against full argument lists - '-l', // Print the process name and process ID - commandToMatch, // command must be a string rather than a list so it matches on all of it - ]); - if (result.exitCode == 0) { - final String processOutput = result.stdout.trim(); - // Process output is formatted like this: - // 89012 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -clonedSourcePackagesDirPath... - final int? pid = int.tryParse(processOutput.split(' ').firstOrNull ?? ''); - if (pid != null && processOutput.endsWith(commandToMatch)) { - _logger.printTrace( - 'Swift Package Manager dependencies are already being fetched by PID $pid', - ); - await _processUtils.run(['kill', '$pid']); - } - } - } - - final Process process = - _swiftPackageFetchProcess ?? - await _processUtils.start(command, workingDirectory: projectPath); - _swiftPackageFetchProcess ??= process; - if (!waitForCompletion) { - return; - } - if (!quiet) { - var printFetchWarnings = false; - _swiftPackageFetchStdoutSubscription ??= process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen((String line) { - if (line.startsWith('Fetching')) { - status?.cancel(); - if (!printFetchWarnings) { - _logger.printStatus( - 'Xcode is fetching Swift Package Manager dependencies. This may take several minutes...', - ); - printFetchWarnings = true; - } - status = _logger.startProgress(' $line...'); - } - }); - } - final stderrBuffer = StringBuffer(); - _swiftPackageFetchStderrSubscription ??= process.stderr - .transform(const Utf8Decoder(reportErrors: false)) - .listen(stderrBuffer.write); - - final int exitCode = await process.exitCode.whenComplete(() async { - await _swiftPackageFetchStdoutSubscription?.cancel(); - await _swiftPackageFetchStderrSubscription?.cancel(); - }); - if (exitCode != 0) { - throwToolExit('Xcode failed to resolve Swift Package Manager dependencies:\n$stderrBuffer'); - } - } finally { - status?.cancel(); - } + await xcodeProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: _xcodebuildProjectCommandArguments( + buildDirectory, + // skipPackageUpdatesAndValidation should be false so that when subsequent xcodebuild + // commands run, packages should already be resolved, downloaded, updated, and validated. + skipPackageUpdatesAndValidation: false, + ), + processUtils: _processUtils, + logger: _logger, + quiet: quiet, + waitForCompletion: waitForCompletion, + ); } Future getInfo( diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index eaa659903e2e1..605d4b7cafda0 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -5,11 +5,16 @@ /// @docImport 'ios/mac.dart'; library; +import 'dart:async'; + import 'package:yaml/yaml.dart' as yaml; +import 'base/common.dart'; import 'base/error_handling_io.dart'; import 'base/file_system.dart'; +import 'base/io.dart'; import 'base/logger.dart'; +import 'base/process.dart'; import 'base/template.dart'; import 'base/utils.dart'; import 'base/version.dart'; @@ -384,6 +389,111 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { } return flavor; } + + /// The process used to fetch Swift packages. + Process? _swiftPackageFetchProcess; + + /// The stdout subscription for the Swift package fetch process. + StreamSubscription? _swiftPackageFetchStdoutSubscription; + + /// The stderr subscription for the Swift package fetch process. + StreamSubscription? _swiftPackageFetchStderrSubscription; + + /// Prefetches Swift packages for the Xcode project if the project has migrated to SwiftPM. + /// + /// If a process is already running from a previous Flutter command, kill it before starting + /// the command. If the process is already running from the same Flutter command, wait for it to + /// complete if [waitForCompletion] is true. + /// + /// If [quiet] is false, it will print a spinner while the command is running and print logs of + /// what Swift packages are being fetched. + Future prefetchSwiftPackages({ + required List xcodebuildProjectCommandArguments, + required ProcessUtils processUtils, + required Logger logger, + bool quiet = true, + bool waitForCompletion = true, + }) async { + Status? status; + try { + final command = [...xcodebuildProjectCommandArguments, '-resolvePackageDependencies']; + if (_swiftPackageFetchProcess == null) { + // Remove the `xcrun` prefixes from the command before comparing because the process name + // will resolve to the actual xcodebuild path, such as this: + // /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild + final int xcodebuildIndex = command.indexOf('xcodebuild'); + if (xcodebuildIndex == -1) { + // This should never happen. The _xcodebuildProjectCommandArguments always includes + // xcodebuild. + throw StateError('Command "${command.join(' ')}" is expected to contain `xcodebuild`.'); + } + final String commandToMatch = command.sublist(xcodebuildIndex).join(' '); + + // Check if process is already running from a previous Flutter command. If it is, kill it + // so we don't have the process running twice. When this process is run twice, it'll cause + // one to error. The new process will pick up where the old one left off. + final RunResult result = await processUtils.run([ + 'pgrep', + '-n', // Select only the newest + '-f', // Match against full argument lists + '-l', // Print the process name and process ID + commandToMatch, // command must be a string rather than a list so it matches on all of it + ]); + if (result.exitCode == 0) { + final String processOutput = result.stdout.trim(); + // Process output is formatted like this: + // 89012 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -clonedSourcePackagesDirPath... + final int? pid = int.tryParse(processOutput.split(' ').firstOrNull ?? ''); + if (pid != null && processOutput.endsWith(commandToMatch)) { + logger.printTrace( + 'Swift Package Manager dependencies are already being fetched by PID $pid', + ); + await processUtils.run(['kill', '$pid']); + } + } + } + + final Process process = + _swiftPackageFetchProcess ?? + await processUtils.start(command, workingDirectory: hostAppRoot.path); + _swiftPackageFetchProcess ??= process; + if (!waitForCompletion) { + return; + } + if (!quiet) { + var printFetchWarnings = false; + _swiftPackageFetchStdoutSubscription ??= process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + if (line.startsWith('Fetching')) { + status?.cancel(); + if (!printFetchWarnings) { + logger.printStatus( + 'Xcode is fetching Swift Package Manager dependencies. This may take several minutes...', + ); + printFetchWarnings = true; + } + status = logger.startProgress(' $line...'); + } + }); + } + final stderrBuffer = StringBuffer(); + _swiftPackageFetchStderrSubscription ??= process.stderr + .transform(const Utf8Decoder(reportErrors: false)) + .listen(stderrBuffer.write); + + final int exitCode = await process.exitCode.whenComplete(() async { + await _swiftPackageFetchStdoutSubscription?.cancel(); + await _swiftPackageFetchStderrSubscription?.cancel(); + }); + if (exitCode != 0) { + throwToolExit('Xcode failed to resolve Swift Package Manager dependencies:\n$stderrBuffer'); + } + } finally { + status?.cancel(); + } + } } /// Represents the iOS sub-project of a Flutter project. diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart index 7f085f7862f59..1b26767428a34 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart @@ -2496,436 +2496,6 @@ flutter: ); }); - testWithoutContext( - 'prefetchSwiftPackages kills previously running process before starting new one', - () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fileSystem.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - stdout: - '12345 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' - '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' - '-resolvePackageDependencies', - ), - const FakeCommand(command: ['kill', '12345']), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - stdout: ''' -Resolve Package Graph - -Fetching from https://github.com/apple/swift-algorithms.git (cached) - -Fetching from https://github.com/apple/swift-numerics.git (cached) - -Creating working copy of package ‘swift-numerics’ - -Creating working copy of package ‘swift-algorithms’ - -Checking out 1.2.2 of package ‘swift-algorithms’ - -Checking out 1.1.1 of package ‘swift-numerics’ - - -Resolved source packages: - swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 - swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 -''', - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - expect( - testLogger.statusText, - contains(''' -Xcode is fetching Swift Package Manager dependencies. This may take several minutes... - Fetching from https://github.com/apple/swift-algorithms.git (cached)... - Fetching from https://github.com/apple/swift-numerics.git (cached)... -'''), - ); - }, - ); - - testWithoutContext('prefetchSwiftPackages does not kill process if pid is invalid', () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fs.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - stdout: - 'abc /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' - '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' - '-resolvePackageDependencies', - ), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - }); - - testWithoutContext( - 'prefetchSwiftPackages does not kill process if command does not match', - () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fs.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - stdout: '12345 xcodebuild something else', - ), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - }, - ); - - testWithoutContext('prefetchSwiftPackages skips running if already completed', () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fs.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - exitCode: 1, - ), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ); - - expect(fakeProcessManager, hasNoRemainingExpectations); - }); - - testWithoutContext('prefetchSwiftPackages does not print when quiet is true', () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fileSystem.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - stdout: - '12345 xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ), - const FakeCommand(command: ['kill', '12345']), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - stdout: ''' -Resolve Package Graph - -Fetching from https://github.com/apple/swift-algorithms.git (cached) - -Fetching from https://github.com/apple/swift-numerics.git (cached) - -Creating working copy of package ‘swift-numerics’ - -Creating working copy of package ‘swift-algorithms’ - -Checking out 1.2.2 of package ‘swift-algorithms’ - -Checking out 1.1.1 of package ‘swift-numerics’ - - -Resolved source packages: - swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 - swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 -''', - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - expect(testLogger.statusText, isEmpty); - }); - - testWithoutContext( - 'prefetchSwiftPackages does not wait for command to complete when waitForCompletion is false', - () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fileSystem.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - final commandCompleter = Completer(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - stdout: - '12345 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' - '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' - '-resolvePackageDependencies', - ), - const FakeCommand(command: ['kill', '12345']), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - stdout: ''' -Resolve Package Graph - -Fetching from https://github.com/apple/swift-algorithms.git (cached) - -Fetching from https://github.com/apple/swift-numerics.git (cached) - -Creating working copy of package ‘swift-numerics’ - -Creating working copy of package ‘swift-algorithms’ - -Checking out 1.2.2 of package ‘swift-algorithms’ - -Checking out 1.1.1 of package ‘swift-numerics’ - - -Resolved source packages: - swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 - swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 -''', - completer: commandCompleter, - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - waitForCompletion: false, - ); - expect(fakeProcessManager, hasNoRemainingExpectations); - expect(testLogger.statusText, isEmpty); - expect(commandCompleter.isCompleted, isFalse); - commandCompleter.complete(); - }, - ); - - testWithoutContext('prefetchSwiftPackages throws exception when resolving fails', () async { - final fs = MemoryFileSystem.test(); - final testLogger = BufferLogger.test(); - final platform = FakePlatform(operatingSystem: 'macos'); - const projectPath = 'path/to/project'; - final Directory buildDirectory = fileSystem.directory('$projectPath/build/ios'); - final fakeProcessManager = FakeProcessManager.empty(); - fakeProcessManager.addCommands([ - kWhichSysctlCommand, - kx64CheckCommand, - FakeCommand( - command: [ - 'pgrep', - '-n', - '-f', - '-l', - 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', - ], - ), - FakeCommand( - command: [ - 'xcrun', - 'xcodebuild', - '-clonedSourcePackagesDirPath', - '/${buildDirectory.path}/SourcePackages', - '-resolvePackageDependencies', - ], - exitCode: 1, - stderr: 'error: some error', - ), - ]); - - final xcodeProjectInterpreter = XcodeProjectInterpreter( - logger: testLogger, - fileSystem: fs, - platform: platform, - processManager: fakeProcessManager, - analytics: const NoOpAnalytics(), - ); - await expectLater( - xcodeProjectInterpreter.prefetchSwiftPackages( - FakeXcodeBasedProject(projectPath, fs), - buildDirectory: buildDirectory, - quiet: false, - ), - throwsToolExit(), - ); - }); - testWithoutContext('swiftPackageCachePath returns the correct cache path', () { final fs = MemoryFileSystem.test(); final Directory buildDirectory = fs.directory('/build/ios'); @@ -2943,9 +2513,12 @@ Resolved source packages: }); } -class FakeXcodeBasedProject extends Fake implements XcodeBasedProject { +class FakeFlutterProject extends Fake implements FlutterProject {} + +class FakeXcodeBasedProject extends IosProject { FakeXcodeBasedProject(this.path, [FileSystem? fileSystem]) - : fs = fileSystem ?? MemoryFileSystem.test(); + : fs = fileSystem ?? MemoryFileSystem.test(), + super.fromFlutter(FakeFlutterProject()); final String path; final FileSystem fs; @@ -2955,4 +2528,10 @@ class FakeXcodeBasedProject extends Fake implements XcodeBasedProject { @override Directory get xcodeProject => fs.directory(path); + + @override + bool get usesSwiftPackageManager => true; + + @override + bool get flutterPluginSwiftPackageInProjectSettings => true; } diff --git a/packages/flutter_tools/test/general.shard/xcode_project_test.dart b/packages/flutter_tools/test/general.shard/xcode_project_test.dart index 5fa6803706f81..95f66a9708199 100644 --- a/packages/flutter_tools/test/general.shard/xcode_project_test.dart +++ b/packages/flutter_tools/test/general.shard/xcode_project_test.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; @@ -19,6 +22,7 @@ import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; +import '../src/fake_process_manager.dart'; import '../src/fakes.dart'; void main() { @@ -560,6 +564,442 @@ void main() { }, ); }); + + group('prefetchSwiftPackages', () { + testWithoutContext('kills previously running process before starting new one', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + stdout: + '12345 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' + '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' + '-resolvePackageDependencies', + ), + const FakeCommand(command: ['kill', '12345']), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + stdout: ''' +Resolve Package Graph + +Fetching from https://github.com/apple/swift-algorithms.git (cached) + +Fetching from https://github.com/apple/swift-numerics.git (cached) + +Creating working copy of package ‘swift-numerics’ + +Creating working copy of package ‘swift-algorithms’ + +Checking out 1.2.2 of package ‘swift-algorithms’ + +Checking out 1.1.1 of package ‘swift-numerics’ + + +Resolved source packages: + swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 + swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 +''', + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect( + testLogger.statusText, + contains(''' +Xcode is fetching Swift Package Manager dependencies. This may take several minutes... + Fetching from https://github.com/apple/swift-algorithms.git (cached)... + Fetching from https://github.com/apple/swift-numerics.git (cached)... +'''), + ); + }); + + testWithoutContext('does not kill process if pid is invalid', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + stdout: + 'abc /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' + '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' + '-resolvePackageDependencies', + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('does not kill process if command does not match', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + stdout: '12345 xcodebuild something else', + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('skips running if already completed', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + exitCode: 1, + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('does not print when quiet is true', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + stdout: + '12345 xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ), + const FakeCommand(command: ['kill', '12345']), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + stdout: ''' +Resolve Package Graph + +Fetching from https://github.com/apple/swift-algorithms.git (cached) + +Fetching from https://github.com/apple/swift-numerics.git (cached) + +Creating working copy of package ‘swift-numerics’ + +Creating working copy of package ‘swift-algorithms’ + +Checking out 1.2.2 of package ‘swift-algorithms’ + +Checking out 1.1.1 of package ‘swift-numerics’ + + +Resolved source packages: + swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 + swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 +''', + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext( + 'does not wait for command to complete when waitForCompletion is false', + () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + final commandCompleter = Completer(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + stdout: + '12345 /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild ' + '-clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages ' + '-resolvePackageDependencies', + ), + const FakeCommand(command: ['kill', '12345']), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + stdout: ''' +Resolve Package Graph + +Fetching from https://github.com/apple/swift-algorithms.git (cached) + +Fetching from https://github.com/apple/swift-numerics.git (cached) + +Creating working copy of package ‘swift-numerics’ + +Creating working copy of package ‘swift-algorithms’ + +Checking out 1.2.2 of package ‘swift-algorithms’ + +Checking out 1.1.1 of package ‘swift-numerics’ + + +Resolved source packages: + swift-numerics: https://github.com/apple/swift-numerics.git @ 1.1.1 + swift-algorithms: https://github.com/apple/swift-algorithms.git @ 1.2.2 +''', + completer: commandCompleter, + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + waitForCompletion: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(testLogger.statusText, isEmpty); + expect(commandCompleter.isCompleted, isFalse); + commandCompleter.complete(); + }, + ); + + testWithoutContext('throws exception when resolving fails', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory buildDirectory = fs.directory('$projectPath/build/ios'); + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${buildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + exitCode: 1, + stderr: 'error: some error', + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = TestIosProject( + FakeFlutterProject(fileSystem: fs), + fs.directory(projectPath), + ); + await expectLater( + iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${buildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ), + throwsToolExit(), + ); + }); + }); }); } @@ -645,3 +1085,16 @@ class FakeCache extends Fake implements Cache { return olderThanToolsStamp; } } + +class TestIosProject extends IosProject { + TestIosProject(super.parent, this.hostAppRoot) : super.fromFlutter(); + + @override + final Directory hostAppRoot; + + @override + bool get usesSwiftPackageManager => true; + + @override + bool get flutterPluginSwiftPackageInProjectSettings => true; +} From eb602bfc75c531e12d6e63bc882e5057c4bdd6a8 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Wed, 13 May 2026 13:33:18 -0500 Subject: [PATCH 2/5] ++ --- packages/flutter_tools/lib/src/ios/xcodeproj.dart | 4 ++-- .../lib/src/macos/darwin_dependency_management.dart | 2 +- .../macos/darwin_dependency_management_test.dart | 2 +- packages/flutter_tools/test/src/context.dart | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 812edaed93e54..113208e7ddbb1 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -193,7 +193,7 @@ class XcodeProjectInterpreter { // All `xcodebuild` project commands will download and resolve Swift packages. // We should always prefetch Swift packages before running any `xcodebuild` project command // to control the output. - await prefetchSwiftPackages(xcodeProject, buildDirectory: buildDirectory, quiet: false); + await prefetchSwiftPackagesForProject(xcodeProject, buildDirectory: buildDirectory, quiet: false); return _xcodebuildProjectCommandArguments( buildDirectory, @@ -387,7 +387,7 @@ class XcodeProjectInterpreter { } /// Prefetches Swift packages for the given Xcode project. - Future prefetchSwiftPackages( + Future prefetchSwiftPackagesForProject( XcodeBasedProject xcodeProject, { required Directory buildDirectory, bool quiet = true, diff --git a/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart index 33605562dda91..db821cdb87441 100644 --- a/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart +++ b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart @@ -70,7 +70,7 @@ class DarwinDependencyManagement { await _swiftPackageManager.generatePluginsSwiftPackage(_plugins, platform, xcodeProject); // Start the SwiftPM dependency resolution in the background. - await _xcodeProjectInterpreter?.prefetchSwiftPackages( + await _xcodeProjectInterpreter?.prefetchSwiftPackagesForProject( xcodeProject, waitForCompletion: false, buildDirectory: _fileSystem.directory( diff --git a/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart index 9285f1830f87b..fe34e12bb30fe 100644 --- a/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart @@ -1680,7 +1680,7 @@ class FakePluginPlatform extends Fake implements PluginPlatform {} class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { @override - Future prefetchSwiftPackages( + Future prefetchSwiftPackagesForProject( XcodeBasedProject xcodeProject, { required Directory buildDirectory, bool quiet = true, diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index e05d242bf8db1..1d735482a83ea 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -406,7 +406,7 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { List xcrunCommand() => ['xcrun']; @override - Future prefetchSwiftPackages( + Future prefetchSwiftPackagesForProject( XcodeBasedProject xcodeProject, { required Directory buildDirectory, bool quiet = true, From d93499f8ba04ec4b64f67563de92f2ba48b97f25 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Wed, 13 May 2026 14:01:28 -0500 Subject: [PATCH 3/5] ++ --- .../general.shard/xcode_project_test.dart | 129 +++++++++++++----- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/packages/flutter_tools/test/general.shard/xcode_project_test.dart b/packages/flutter_tools/test/general.shard/xcode_project_test.dart index 95f66a9708199..bf4dbe2148ea8 100644 --- a/packages/flutter_tools/test/general.shard/xcode_project_test.dart +++ b/packages/flutter_tools/test/general.shard/xcode_project_test.dart @@ -619,10 +619,7 @@ Resolved source packages: ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -677,10 +674,7 @@ Xcode is fetching Swift Package Manager dependencies. This may take several minu ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -724,10 +718,7 @@ Xcode is fetching Swift Package Manager dependencies. This may take several minu ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -771,10 +762,7 @@ Xcode is fetching Swift Package Manager dependencies. This may take several minu ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -853,10 +841,7 @@ Resolved source packages: ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -928,10 +913,7 @@ Resolved source packages: ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ 'xcrun', @@ -980,10 +962,7 @@ Resolved source packages: ]); final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); - final iosProject = TestIosProject( - FakeFlutterProject(fileSystem: fs), - fs.directory(projectPath), - ); + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); await expectLater( iosProject.prefetchSwiftPackages( xcodebuildProjectCommandArguments: [ @@ -999,6 +978,83 @@ Resolved source packages: throwsToolExit(), ); }); + testWithoutContext('prefetchSwiftPackages can run for both platforms', () async { + final fs = MemoryFileSystem.test(); + final testLogger = BufferLogger.test(); + const projectPath = 'path/to/project'; + final Directory iosBuildDirectory = fs.directory('$projectPath/build/ios'); + final Directory macosBuildDirectory = fs.directory('$projectPath/build/macos'); + + final fakeProcessManager = FakeProcessManager.empty(); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${iosBuildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + exitCode: 1, + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${iosBuildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + ), + FakeCommand( + command: [ + 'pgrep', + '-n', + '-f', + '-l', + 'xcodebuild -clonedSourcePackagesDirPath /${macosBuildDirectory.path}/SourcePackages -resolvePackageDependencies', + ], + exitCode: 1, + ), + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${macosBuildDirectory.path}/SourcePackages', + '-resolvePackageDependencies', + ], + ), + ]); + final processUtils = ProcessUtils(logger: testLogger, processManager: fakeProcessManager); + + final iosProject = FakeIosProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); + await iosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${iosBuildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + + final macosProject = FakeMacOSProject.fromFlutter(FakeFlutterProject(fileSystem: fs)); + await macosProject.prefetchSwiftPackages( + xcodebuildProjectCommandArguments: [ + 'xcrun', + 'xcodebuild', + '-clonedSourcePackagesDirPath', + '/${macosBuildDirectory.path}/SourcePackages', + ], + processUtils: processUtils, + logger: testLogger, + quiet: false, + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); }); }); } @@ -1086,15 +1142,22 @@ class FakeCache extends Fake implements Cache { } } -class TestIosProject extends IosProject { - TestIosProject(super.parent, this.hostAppRoot) : super.fromFlutter(); +class FakeIosProject extends IosProject { + FakeIosProject.fromFlutter(super.parent) : super.fromFlutter(); @override - final Directory hostAppRoot; + bool usesSwiftPackageManager = true; + + @override + bool flutterPluginSwiftPackageInProjectSettings = true; +} + +class FakeMacOSProject extends MacOSProject { + FakeMacOSProject.fromFlutter(super.parent) : super.fromFlutter(); @override - bool get usesSwiftPackageManager => true; + bool usesSwiftPackageManager = true; @override - bool get flutterPluginSwiftPackageInProjectSettings => true; + bool flutterPluginSwiftPackageInProjectSettings = true; } From 8cc309d94e3a1361bd6e85198e4fefce83bbc738 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Thu, 21 May 2026 11:00:15 -0500 Subject: [PATCH 4/5] format --- packages/flutter_tools/lib/src/ios/xcodeproj.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 16334442d99d0..67e0b95d8bf35 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -193,7 +193,11 @@ class XcodeProjectInterpreter { // All `xcodebuild` project commands will download and resolve Swift packages. // We should always prefetch Swift packages before running any `xcodebuild` project command // to control the output. - await prefetchSwiftPackagesForProject(xcodeProject, buildDirectory: buildDirectory, quiet: false); + await prefetchSwiftPackagesForProject( + xcodeProject, + buildDirectory: buildDirectory, + quiet: false, + ); return _xcodebuildProjectCommandArguments( buildDirectory, From bf699029ee36623f0df85fe98ca434dd2d737988 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth Date: Thu, 21 May 2026 11:00:58 -0500 Subject: [PATCH 5/5] ++ --- packages/flutter_tools/lib/src/ios/xcodeproj.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 67e0b95d8bf35..d5cf641287e16 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -20,7 +20,6 @@ import '../base/terminal.dart'; import '../base/utils.dart'; import '../base/version.dart'; import '../build_info.dart'; -import '../convert.dart'; import '../xcode_project.dart'; final _settingExpr = RegExp(r'(\w+)\s*=\s*(.*)$');