Use cached Xcode build settings during iOS build (#80904)
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 35d9b1d..eea1947 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -176,9 +176,15 @@ } Map<String, String> autoSigningConfigs; + + final Map<String, String> buildSettings = await app.project.buildSettingsForBuildInfo( + buildInfo, + environmentType: buildForDevice ? EnvironmentType.physical : EnvironmentType.simulator, + ) ?? <String, String>{}; + if (codesign && buildForDevice) { autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam( - buildSettings: await app.project.buildSettingsForBuildInfo(buildInfo), + buildSettings: buildSettings, processManager: globals.processManager, logger: globals.logger, config: globals.config, @@ -353,45 +359,6 @@ ); globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds)); - // Run -showBuildSettings again but with the exact same parameters as the - // build. showBuildSettings is reported to occasionally timeout. Here, we give - // it a lot of wiggle room (locally on Flutter Gallery, this takes ~1s). - // When there is a timeout, we retry once. See issue #35988. - final List<String> showBuildSettingsCommand = (List<String> - .of(buildCommands) - ..add('-showBuildSettings')) - // Undocumented behavior: xcodebuild craps out if -showBuildSettings - // is used together with -allowProvisioningUpdates or - // -allowProvisioningDeviceRegistration and freezes forever. - .where((String buildCommand) { - return !const <String>[ - '-allowProvisioningUpdates', - '-allowProvisioningDeviceRegistration', - ].contains(buildCommand); - }).toList(); - const Duration showBuildSettingsTimeout = Duration(minutes: 1); - Map<String, String> buildSettings; - try { - final RunResult showBuildSettingsResult = await globals.processUtils.run( - showBuildSettingsCommand, - throwOnError: true, - workingDirectory: app.project.hostAppRoot.path, - timeout: showBuildSettingsTimeout, - timeoutRetries: 1, - ); - final String showBuildSettings = showBuildSettingsResult.stdout.trim(); - buildSettings = parseXcodeBuildSettings(showBuildSettings); - } on ProcessException catch (e) { - if (e.toString().contains('timed out')) { - BuildEvent('xcode-show-build-settings-timeout', - type: 'ios', - command: showBuildSettingsCommand.join(' '), - flutterUsage: globals.flutterUsage, - ).send(); - } - rethrow; - } - if (buildResult.exitCode != 0) { globals.printStatus('Failed to build iOS app'); if (buildResult.stderr.isNotEmpty) {
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index ea544e8..2691966 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -166,10 +166,12 @@ /// target (by default this is Runner). Future<Map<String, String>> getBuildSettings( String projectPath, { - String? scheme, + required XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { final Status status = _logger.startSpinner(); + final String? scheme = buildContext.scheme; + final String? configuration = buildContext.configuration; final List<String> showBuildSettingsCommand = <String>[ ...xcrunCommand(), 'xcodebuild', @@ -177,7 +179,12 @@ _fileSystem.path.absolute(projectPath), if (scheme != null) ...<String>['-scheme', scheme], + if (configuration != null) + ...<String>['-configuration', configuration], + if (buildContext.environmentType == EnvironmentType.simulator) + ...<String>['-sdk', 'iphonesimulator'], '-showBuildSettings', + 'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}', ...environmentVariablesAsXcodeBuildSettings(_platform) ]; try { @@ -201,7 +208,7 @@ flutterUsage: _usage, ).send(); } - _logger.printTrace('Unexpected failure to get the build settings: $error.'); + _logger.printTrace('Unexpected failure to get Xcode build settings: $error.'); return const <String, String>{}; } finally { status.stop(); @@ -282,6 +289,29 @@ return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!); } +@immutable +class XcodeProjectBuildContext { + const XcodeProjectBuildContext({this.scheme, this.configuration, this.environmentType = EnvironmentType.physical}); + + final String? scheme; + final String? configuration; + final EnvironmentType environmentType; + + @override + int get hashCode => scheme.hashCode ^ configuration.hashCode ^ environmentType.hashCode; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + return other is XcodeProjectBuildContext && + other.scheme == scheme && + other.configuration == configuration && + other.environmentType == environmentType; + } +} + /// Information about an Xcode project. /// /// Represents the output of `xcodebuild -list`. @@ -365,7 +395,7 @@ }); } - void reportFlavorNotFoundAndExit() { + Never reportFlavorNotFoundAndExit() { _logger.printError(''); if (definesCustomSchemes) { _logger.printError('The Xcode project defines schemes: ${schemes.join(', ')}'); @@ -377,7 +407,10 @@ /// Returns unique build configuration matching [buildInfo] and [scheme], or /// null, if there is no unique best match. - String? buildConfigurationFor(BuildInfo buildInfo, String scheme) { + String? buildConfigurationFor(BuildInfo? buildInfo, String scheme) { + if (buildInfo == null) { + return null; + } final String expectedConfiguration = expectedBuildConfigurationFor(buildInfo, scheme); if (hasBuildConfigurationForBuildMode(expectedConfiguration)) { return expectedConfiguration;
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 6744c03..50f9e12 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -248,6 +248,7 @@ } else { final bool isSwift = (await _xcodeProjectInterpreter.getBuildSettings( runnerProject.path, + buildContext: const XcodeProjectBuildContext(), )).containsKey('SWIFT_VERSION'); podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc'; }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 7f18902..0dea5ca 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -593,11 +593,10 @@ /// The build settings for the host app of this project, as a detached map. /// /// Returns null, if iOS tooling is unavailable. - Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async { + Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo, { EnvironmentType environmentType = EnvironmentType.physical }) async { if (!existsSync()) { return null; } - _buildSettingsByScheme ??= <String, Map<String, String>>{}; final XcodeProjectInfo info = await projectInfo(); if (info == null) { return null; @@ -608,9 +607,15 @@ info.reportFlavorNotFoundAndExit(); } - return _buildSettingsByScheme[scheme] ??= await _xcodeProjectBuildSettings(scheme); + final String configuration = (await projectInfo()).buildConfigurationFor( + buildInfo, + scheme, + ); + final XcodeProjectBuildContext buildContext = XcodeProjectBuildContext(environmentType: environmentType, scheme: scheme, configuration: configuration); + return _buildSettingsByBuildContext[buildContext] ??= await _xcodeProjectBuildSettings(buildContext); } - Map<String, Map<String, String>> _buildSettingsByScheme; + + final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{}; Future<XcodeProjectInfo> projectInfo() async { if (!xcodeProject.existsSync() || !globals.xcodeProjectInterpreter.isInstalled) { @@ -620,13 +625,14 @@ } XcodeProjectInfo _projectInfo; - Future<Map<String, String>> _xcodeProjectBuildSettings(String scheme) async { + Future<Map<String, String>> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async { if (!globals.xcodeProjectInterpreter.isInstalled) { return null; } + final Map<String, String> buildSettings = await globals.xcodeProjectInterpreter.getBuildSettings( xcodeProject.path, - scheme: scheme, + buildContext: buildContext, ); if (buildSettings != null && buildSettings.isNotEmpty) { // No timeouts, flakes, or errors.
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart index 29c5781..f0a12d8 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart
@@ -22,12 +22,14 @@ @override Future<Map<String, String>> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return <String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'DEVELOPMENT_TEAM': 'abc', + 'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos', + 'WRAPPER_NAME': 'Runner.app', }; } } @@ -92,12 +94,16 @@ // Creates a FakeCommand for the xcodebuild call to build the app // in the given configuration. - FakeCommand _setUpFakeXcodeBuildHandler({ bool verbose = false, bool showBuildSettings = false, void Function() onRun }) { + FakeCommand _setUpFakeXcodeBuildHandler({ bool verbose = false, bool simulator = false, void Function() onRun }) { return FakeCommand( command: <String>[ 'xcrun', 'xcodebuild', - '-configuration', 'Release', + '-configuration', + if (simulator) + 'Debug' + else + 'Release', if (verbose) 'VERBOSE_SCRIPT_LOGGING=YES' else @@ -105,11 +111,13 @@ '-workspace', 'Runner.xcworkspace', '-scheme', 'Runner', 'BUILD_DIR=/build/ios', - '-sdk', 'iphoneos', + '-sdk', + if (simulator) + 'iphonesimulator' + else + 'iphoneos', 'FLUTTER_SUPPRESS_ANALYTICS=true', 'COMPILER_INDEX_STORE_ENABLE=NO', - if (showBuildSettings) - '-showBuildSettings', ], stdout: ''' TARGET_BUILD_DIR=build/ios/Release-iphoneos @@ -179,7 +187,26 @@ _setUpFakeXcodeBuildHandler(onRun: () { fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true); }), - _setUpFakeXcodeBuildHandler(showBuildSettings: true), + _setUpRsyncCommand(), + ]), + Platform: () => macosPlatform, + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + }); + + testUsingContext('ios simulator build invokes xcode build', () async { + final BuildCommand command = BuildCommand(); + _createMinimalMockProjectFiles(); + + await createTestCommandRunner(command).run( + const <String>['build', 'ios', '--simulator', '--no-pub'] + ); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ + xattrCommand, + _setUpFakeXcodeBuildHandler(simulator: true, onRun: () { + fileSystem.directory('build/ios/Debug-iphonesimulator/Runner.app').createSync(recursive: true); + }), _setUpRsyncCommand(), ]), Platform: () => macosPlatform, @@ -200,7 +227,6 @@ _setUpFakeXcodeBuildHandler(verbose: true, onRun: () { fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true); }), - _setUpFakeXcodeBuildHandler(verbose: true, showBuildSettings: true), _setUpRsyncCommand(), ]), Platform: () => macosPlatform, @@ -241,7 +267,6 @@ ..createSync(recursive: true) ..writeAsStringSync('{}'); }), - _setUpFakeXcodeBuildHandler(showBuildSettings: true), _setUpRsyncCommand(onRun: () => fileSystem.file('build/ios/iphoneos/Runner.app/Frameworks/App.framework/App') ..createSync(recursive: true) ..writeAsBytesSync(List<int>.generate(10000, (int index) => 0))),
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart index b958aa5..50d359a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart
@@ -21,7 +21,7 @@ @override Future<Map<String, String>> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return <String, String>{ @@ -81,7 +81,7 @@ // Creates a FakeCommand for the xcodebuild call to build the app // in the given configuration. - FakeCommand setUpFakeXcodeBuildHandler({ bool verbose = false, bool showBuildSettings = false, void Function() onRun }) { + FakeCommand setUpFakeXcodeBuildHandler({ bool verbose = false, void Function() onRun }) { return FakeCommand( command: <String>[ 'xcrun', @@ -98,8 +98,6 @@ 'COMPILER_INDEX_STORE_ENABLE=NO', '-archivePath', '/build/ios/archive/Runner', 'archive', - if (showBuildSettings) - '-showBuildSettings', ], stdout: 'STDOUT STUFF', onRun: onRun, @@ -224,7 +222,6 @@ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ xattrCommand, setUpFakeXcodeBuildHandler(), - setUpFakeXcodeBuildHandler(showBuildSettings: true), ]), Platform: () => macosPlatform, XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), @@ -242,7 +239,6 @@ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ xattrCommand, setUpFakeXcodeBuildHandler(verbose: true), - setUpFakeXcodeBuildHandler(verbose: true, showBuildSettings: true), ]), Platform: () => macosPlatform, XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), @@ -303,7 +299,6 @@ ..createSync(recursive: true) ..writeAsStringSync('{}'); }), - setUpFakeXcodeBuildHandler(showBuildSettings: true), ]), Platform: () => macosPlatform, FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform), @@ -335,7 +330,6 @@ ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ xattrCommand, setUpFakeXcodeBuildHandler(), - setUpFakeXcodeBuildHandler(showBuildSettings: true), exportArchiveCommand, ]), Platform: () => macosPlatform,
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index 87e76b4..9492ea6 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
@@ -105,6 +105,13 @@ )); } ); + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))) + .thenAnswer((_) async => <String, String>{ + 'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos', + 'WRAPPER_NAME': 'My Super Awesome App.app', + 'DEVELOPMENT_TEAM': '3333CCCC33', + }); + xcode = Xcode.test(processManager: FakeProcessManager.any(), xcodeProjectInterpreter: mockXcodeProjectInterpreter); fileSystem.file('foo/.packages') ..createSync(recursive: true) @@ -125,11 +132,6 @@ processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand(const FakeCommand(command: <String>[...kRunReleaseArgs, '-showBuildSettings'], stdout: r''' - TARGET_BUILD_DIR=build/ios/Release-iphoneos - WRAPPER_NAME=My Super Awesome App.app - ''' - )); processManager.addCommand(const FakeCommand(command: <String>[ 'rsync', '-av', @@ -174,95 +176,6 @@ Xcode: () => xcode, }); - testUsingContext('with flaky buildSettings call', () async { - LaunchResult launchResult; - FakeAsync().run((FakeAsync time) { - final IOSDevice iosDevice = setUpIOSDevice( - fileSystem: fileSystem, - processManager: processManager, - logger: logger, - artifacts: artifacts, - ); - setUpIOSProject(fileSystem); - final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); - final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); - fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); - - processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); - processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - // The first showBuildSettings call should timeout. - processManager.addCommand( - const FakeCommand( - command: <String>[...kRunReleaseArgs, '-showBuildSettings'], - duration: Duration(minutes: 5), // this is longer than the timeout of 1 minute. - )); - // The second call succeeds and is made after the first times out. - processManager.addCommand( - const FakeCommand( - command: <String>[...kRunReleaseArgs, '-showBuildSettings'], - exitCode: 0, - stdout: r''' - TARGET_BUILD_DIR=build/ios/Release-iphoneos - WRAPPER_NAME=My Super Awesome App.app - ''' - )); - processManager.addCommand(const FakeCommand(command: <String>[ - 'rsync', - '-av', - '--delete', - 'build/ios/Release-iphoneos/My Super Awesome App.app', - 'build/ios/iphoneos', - ])); - processManager.addCommand(FakeCommand( - command: <String>[ - iosDeployPath, - '--id', - '123', - '--bundle', - 'build/ios/iphoneos/My Super Awesome App.app', - '--app_deltas', - 'build/ios/app-delta', - '--no-wifi', - '--justlaunch', - '--args', - const <String>[ - '--enable-dart-profiling', - '--disable-service-auth-codes', - ].join(' ') - ]) - ); - - iosDevice.startApp( - buildableIOSApp, - debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), - platformArgs: <String, Object>{}, - ).then((LaunchResult result) { - launchResult = result; - }); - - // Elapse duration for process timeout. - time.flushMicrotasks(); - time.elapse(const Duration(minutes: 1)); - - // Elapse duration for overall process timer. - time.flushMicrotasks(); - time.elapse(const Duration(minutes: 5)); - - time.flushTimers(); - }); - - expect(launchResult?.started, true); - expect(fileSystem.directory('build/ios/iphoneos'), exists); - expect(processManager, hasNoRemainingExpectations); - }, overrides: <Type, Generator>{ - ProcessManager: () => processManager, - FileSystem: () => fileSystem, - Logger: () => logger, - Platform: () => macPlatform, - XcodeProjectInterpreter: () => mockXcodeProjectInterpreter, - Xcode: () => xcode, - }); - testUsingContext('with concurrent build failures', () async { final IOSDevice iosDevice = setUpIOSDevice( fileSystem: fileSystem, @@ -284,11 +197,6 @@ stdout: kConcurrentBuildErrorMessage, )); processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand( - const FakeCommand( - command: <String>[...kRunReleaseArgs, '-showBuildSettings'], - exitCode: 0, - )); processManager.addCommand(FakeCommand( command: <String>[ iosDeployPath,
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 6e81814..94c3ed4 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -46,7 +46,7 @@ ); }); - testWithoutContext('xcodebuild build settings flakes', () async { + testUsingContext('xcodebuild build settings flakes', () async { const Duration delay = Duration(seconds: 1); processManager.processFactory = mocks.flakyProcessFactory( flakes: 1, @@ -59,8 +59,7 @@ when(processManager.runSync(<String>['sysctl', 'hw.optional.arm64'])) .thenReturn(ProcessResult(0, 1, '', '')); - expect(await xcodeProjectInterpreter.getBuildSettings( - '', scheme: 'Runner', timeout: delay), + expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Runner'), timeout: delay), const <String, String>{}); // build settings times out and is killed once, then succeeds. verify(processManager.killPid(any)).called(1); @@ -292,12 +291,12 @@ expect(fakeProcessManager.hasRemainingExpectations, isFalse); }); - testWithoutContext('xcodebuild build settings is empty when xcodebuild failed to get the build settings', () async { + testUsingContext('xcodebuild build settings is empty when xcodebuild failed to get the build settings', () async { platform.environment = const <String, String>{}; - fakeProcessManager.addCommands(const <FakeCommand>[ + fakeProcessManager.addCommands(<FakeCommand>[ kWhichSysctlCommand, - FakeCommand( + const FakeCommand( command: <String>[ 'sysctl', 'hw.optional.arm64', @@ -312,20 +311,26 @@ '/', '-scheme', 'Free', - '-showBuildSettings' + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', ], exitCode: 1, ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings('', scheme: 'Free'), const <String, String>{}); + expect( + await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Free')), + const <String, String>{}); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), }); - testWithoutContext('build settings accepts an empty scheme', () async { + testUsingContext('build settings passes in the simulator SDK', () async { platform.environment = const <String, String>{}; - fakeProcessManager.addCommands(const <FakeCommand>[ + fakeProcessManager.addCommands(<FakeCommand>[ kWhichSysctlCommand, kARMCheckCommand, FakeCommand( @@ -334,17 +339,56 @@ 'xcodebuild', '-project', '/', - '-showBuildSettings' + '-sdk', + 'iphonesimulator', + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', ], exitCode: 1, ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings(''), const <String, String>{}); + expect( + await xcodeProjectInterpreter.getBuildSettings( + '', + buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator), + ), + const <String, String>{}, + ); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), }); - testWithoutContext('xcodebuild build settings contains Flutter Xcode environment variables', () async { + testUsingContext('build settings accepts an empty scheme', () async { + platform.environment = const <String, String>{}; + + fakeProcessManager.addCommands(<FakeCommand>[ + kWhichSysctlCommand, + kARMCheckCommand, + FakeCommand( + command: <String>[ + 'xcrun', + 'xcodebuild', + '-project', + '/', + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', + ], + exitCode: 1, + ), + ]); + + expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext()), + const <String, String>{}); + expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('xcodebuild build settings contains Flutter Xcode environment variables', () async { platform.environment = const <String, String>{ 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', 'FLUTTER_XCODE_ARCHS': 'arm64' @@ -361,13 +405,19 @@ '-scheme', 'Free', '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', 'CODE_SIGN_STYLE=Manual', 'ARCHS=arm64' ], ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings('', scheme: 'Free'), const <String, String>{}); + expect( + await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Free')), + const <String, String>{}); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), }); testWithoutContext('xcodebuild clean contains Flutter Xcode environment variables', () async {
diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index 45764b5..2e7fca2 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart
@@ -794,7 +794,7 @@ @override Future<Map<String, String>> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async => buildSettings;
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index dfdd038..5f05068 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -397,7 +397,7 @@ testWithMocks('from build settings, if no plist', () async { final FlutterProject project = await someProject(); project.ios.xcodeProject.createSync(); - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future<Map<String,String>>.value(<String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -428,7 +428,7 @@ testWithMocks('from build settings and plist, if default variable', () async { final FlutterProject project = await someProject(); project.ios.xcodeProject.createSync(); - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future<Map<String,String>>.value(<String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -447,7 +447,7 @@ final FlutterProject project = await someProject(); project.ios.xcodeProject.createSync(); project.ios.defaultHostInfoPlist.createSync(recursive: true); - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future<Map<String,String>>.value(<String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -478,7 +478,7 @@ testWithMocks('handles case insensitive flavor', () async { final FlutterProject project = await someProject(); project.ios.xcodeProject.createSync(); - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future<Map<String,String>>.value(<String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -550,7 +550,7 @@ testUsingContext('app product name xcodebuild settings', () async { final FlutterProject project = await someProject(); project.ios.xcodeProject.createSync(); - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer((_) { + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer((_) { return Future<Map<String,String>>.value(<String, String>{ 'FULL_PRODUCT_NAME': 'My App.app' }); @@ -667,7 +667,7 @@ group('with bundle identifier', () { setUp(() { - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future<Map<String,String>>.value(<String, String>{ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index b871a61..648396a 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart
@@ -307,7 +307,7 @@ @override Future<Map<String, String>> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return <String, String>{};