diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 35d9b1d0e7..eea194798e 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -176,9 +176,15 @@ Future buildXcodeProject({ } Map autoSigningConfigs; + + final Map buildSettings = await app.project.buildSettingsForBuildInfo( + buildInfo, + environmentType: buildForDevice ? EnvironmentType.physical : EnvironmentType.simulator, + ) ?? {}; + 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 @@ Future buildXcodeProject({ ); 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 showBuildSettingsCommand = (List - .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 [ - '-allowProvisioningUpdates', - '-allowProvisioningDeviceRegistration', - ].contains(buildCommand); - }).toList(); - const Duration showBuildSettingsTimeout = Duration(minutes: 1); - Map 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 ea544e8fa4..2691966612 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart @@ -166,10 +166,12 @@ class XcodeProjectInterpreter { /// target (by default this is Runner). Future> 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 showBuildSettingsCommand = [ ...xcrunCommand(), 'xcodebuild', @@ -177,7 +179,12 @@ class XcodeProjectInterpreter { _fileSystem.path.absolute(projectPath), if (scheme != null) ...['-scheme', scheme], + if (configuration != null) + ...['-configuration', configuration], + if (buildContext.environmentType == EnvironmentType.simulator) + ...['-sdk', 'iphonesimulator'], '-showBuildSettings', + 'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}', ...environmentVariablesAsXcodeBuildSettings(_platform) ]; try { @@ -201,7 +208,7 @@ class XcodeProjectInterpreter { 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 {}; } finally { status.stop(); @@ -282,6 +289,29 @@ String substituteXcodeVariables(String str, Map xcodeBuildSettin 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 @@ class XcodeProjectInfo { }); } - void reportFlavorNotFoundAndExit() { + Never reportFlavorNotFoundAndExit() { _logger.printError(''); if (definesCustomSchemes) { _logger.printError('The Xcode project defines schemes: ${schemes.join(', ')}'); @@ -377,7 +407,10 @@ class XcodeProjectInfo { /// 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 6744c03d0f..50f9e124a1 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -248,6 +248,7 @@ class CocoaPods { } 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 7f18902153..0dea5ca97d 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -593,11 +593,10 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { /// The build settings for the host app of this project, as a detached map. /// /// Returns null, if iOS tooling is unavailable. - Future> buildSettingsForBuildInfo(BuildInfo buildInfo) async { + Future> buildSettingsForBuildInfo(BuildInfo buildInfo, { EnvironmentType environmentType = EnvironmentType.physical }) async { if (!existsSync()) { return null; } - _buildSettingsByScheme ??= >{}; final XcodeProjectInfo info = await projectInfo(); if (info == null) { return null; @@ -608,9 +607,15 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { 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> _buildSettingsByScheme; + + final Map> _buildSettingsByBuildContext = >{}; Future projectInfo() async { if (!xcodeProject.existsSync() || !globals.xcodeProjectInterpreter.isInstalled) { @@ -620,13 +625,14 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { } XcodeProjectInfo _projectInfo; - Future> _xcodeProjectBuildSettings(String scheme) async { + Future> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async { if (!globals.xcodeProjectInterpreter.isInstalled) { return null; } + final Map 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 29c5781a68..f0a12d87f5 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 @@ class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInter @override Future> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return { 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'DEVELOPMENT_TEAM': 'abc', + 'TARGET_BUILD_DIR': 'build/ios/Release-iphoneos', + 'WRAPPER_NAME': 'Runner.app', }; } } @@ -92,12 +94,16 @@ void main() { // 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: [ 'xcrun', 'xcodebuild', - '-configuration', 'Release', + '-configuration', + if (simulator) + 'Debug' + else + 'Release', if (verbose) 'VERBOSE_SCRIPT_LOGGING=YES' else @@ -105,11 +111,13 @@ void main() { '-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 @@ void main() { _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 ['build', 'ios', '--simulator', '--no-pub'] + ); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list([ + xattrCommand, + _setUpFakeXcodeBuildHandler(simulator: true, onRun: () { + fileSystem.directory('build/ios/Debug-iphonesimulator/Runner.app').createSync(recursive: true); + }), _setUpRsyncCommand(), ]), Platform: () => macosPlatform, @@ -200,7 +227,6 @@ void main() { _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 @@ void main() { ..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.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 b958aa559c..50d359a64d 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 @@ class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInter @override Future> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return { @@ -81,7 +81,7 @@ void main() { // 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: [ 'xcrun', @@ -98,8 +98,6 @@ void main() { 'COMPILER_INDEX_STORE_ENABLE=NO', '-archivePath', '/build/ios/archive/Runner', 'archive', - if (showBuildSettings) - '-showBuildSettings', ], stdout: 'STDOUT STUFF', onRun: onRun, @@ -224,7 +222,6 @@ void main() { ProcessManager: () => FakeProcessManager.list([ xattrCommand, setUpFakeXcodeBuildHandler(), - setUpFakeXcodeBuildHandler(showBuildSettings: true), ]), Platform: () => macosPlatform, XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), @@ -242,7 +239,6 @@ void main() { ProcessManager: () => FakeProcessManager.list([ xattrCommand, setUpFakeXcodeBuildHandler(verbose: true), - setUpFakeXcodeBuildHandler(verbose: true, showBuildSettings: true), ]), Platform: () => macosPlatform, XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), @@ -303,7 +299,6 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('{}'); }), - setUpFakeXcodeBuildHandler(showBuildSettings: true), ]), Platform: () => macosPlatform, FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform), @@ -335,7 +330,6 @@ void main() { ProcessManager: () => FakeProcessManager.list([ 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 87e76b408d..9492ea6bb4 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 @@ void main() { )); } ); + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))) + .thenAnswer((_) async => { + '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 @@ void main() { processManager.addCommand(FakeCommand(command: _xattrArgs(flutterProject))); processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand(const FakeCommand(command: [...kRunReleaseArgs, '-showBuildSettings'], stdout: r''' - TARGET_BUILD_DIR=build/ios/Release-iphoneos - WRAPPER_NAME=My Super Awesome App.app - ''' - )); processManager.addCommand(const FakeCommand(command: [ 'rsync', '-av', @@ -174,95 +176,6 @@ void main() { 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: [...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: [...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: [ - 'rsync', - '-av', - '--delete', - 'build/ios/Release-iphoneos/My Super Awesome App.app', - 'build/ios/iphoneos', - ])); - processManager.addCommand(FakeCommand( - command: [ - iosDeployPath, - '--id', - '123', - '--bundle', - 'build/ios/iphoneos/My Super Awesome App.app', - '--app_deltas', - 'build/ios/app-delta', - '--no-wifi', - '--justlaunch', - '--args', - const [ - '--enable-dart-profiling', - '--disable-service-auth-codes', - ].join(' ') - ]) - ); - - iosDevice.startApp( - buildableIOSApp, - debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), - platformArgs: {}, - ).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: { - 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 @@ void main() { stdout: kConcurrentBuildErrorMessage, )); processManager.addCommand(const FakeCommand(command: kRunReleaseArgs)); - processManager.addCommand( - const FakeCommand( - command: [...kRunReleaseArgs, '-showBuildSettings'], - exitCode: 0, - )); processManager.addCommand(FakeCommand( command: [ 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 6e81814b6b..94c3ed46a5 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 @@ void main() { ); }); - 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 @@ void main() { when(processManager.runSync(['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 {}); // build settings times out and is killed once, then succeeds. verify(processManager.killPid(any)).called(1); @@ -292,12 +291,12 @@ void main() { 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 {}; - fakeProcessManager.addCommands(const [ + fakeProcessManager.addCommands([ kWhichSysctlCommand, - FakeCommand( + const FakeCommand( command: [ 'sysctl', 'hw.optional.arm64', @@ -312,20 +311,26 @@ void main() { '/', '-scheme', 'Free', - '-showBuildSettings' + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', ], exitCode: 1, ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings('', scheme: 'Free'), const {}); + expect( + await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Free')), + const {}); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: { + 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 {}; - fakeProcessManager.addCommands(const [ + fakeProcessManager.addCommands([ kWhichSysctlCommand, kARMCheckCommand, FakeCommand( @@ -334,17 +339,56 @@ void main() { 'xcodebuild', '-project', '/', - '-showBuildSettings' + '-sdk', + 'iphonesimulator', + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', ], exitCode: 1, ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings(''), const {}); + expect( + await xcodeProjectInterpreter.getBuildSettings( + '', + buildContext: const XcodeProjectBuildContext(environmentType: EnvironmentType.simulator), + ), + const {}, + ); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: { + 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 {}; + + fakeProcessManager.addCommands([ + kWhichSysctlCommand, + kARMCheckCommand, + FakeCommand( + command: [ + 'xcrun', + 'xcodebuild', + '-project', + '/', + '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', + ], + exitCode: 1, + ), + ]); + + expect(await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext()), + const {}); + expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('xcodebuild build settings contains Flutter Xcode environment variables', () async { platform.environment = const { 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', 'FLUTTER_XCODE_ARCHS': 'arm64' @@ -361,13 +405,19 @@ void main() { '-scheme', 'Free', '-showBuildSettings', + 'BUILD_DIR=${fileSystem.path.absolute('build', 'ios')}', 'CODE_SIGN_STYLE=Manual', 'ARCHS=arm64' ], ), ]); - expect(await xcodeProjectInterpreter.getBuildSettings('', scheme: 'Free'), const {}); + expect( + await xcodeProjectInterpreter.getBuildSettings('', buildContext: const XcodeProjectBuildContext(scheme: 'Free')), + const {}); expect(fakeProcessManager.hasRemainingExpectations, isFalse); + }, overrides: { + 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 45764b58cc..2e7fca2cad 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 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete @override Future> 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 dfdd03895e..5f05068689 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 @@ apply plugin: 'kotlin-android' 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>.value({ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -428,7 +428,7 @@ apply plugin: 'kotlin-android' 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>.value({ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -447,7 +447,7 @@ apply plugin: 'kotlin-android' 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>.value({ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -478,7 +478,7 @@ apply plugin: 'kotlin-android' 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>.value({ 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', @@ -550,7 +550,7 @@ apply plugin: 'kotlin-android' 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>.value({ 'FULL_PRODUCT_NAME': 'My App.app' }); @@ -667,7 +667,7 @@ apply plugin: 'kotlin-android' group('with bundle identifier', () { setUp(() { - when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer( + when(mockXcodeProjectInterpreter.getBuildSettings(any, buildContext: anyNamed('buildContext'))).thenAnswer( (_) { return Future>.value({ '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 b871a61c08..648396a493 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -307,7 +307,7 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { @override Future> getBuildSettings( String projectPath, { - String scheme, + XcodeProjectBuildContext buildContext, Duration timeout = const Duration(minutes: 1), }) async { return {};