Pass environment variables through to xcodebuild (#43553)
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index e29c1df..d872f19 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -464,6 +464,7 @@ // e.g. `flutter build bundle`. buildCommands.add('FLUTTER_SUPPRESS_ANALYTICS=true'); buildCommands.add('COMPILER_INDEX_STORE_ENABLE=NO'); + buildCommands.addAll(environmentVariablesAsXcodeBuildSettings()); final Stopwatch sw = Stopwatch()..start(); initialBuildStatus = logger.startProgress('Running Xcode build...', timeout: timeoutConfiguration.fastOperation);
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 9244719..40bdd7d 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -293,9 +293,10 @@ '-target', target, '-showBuildSettings', + ...environmentVariablesAsXcodeBuildSettings() ]; try { - // showBuildSettings is reported to ocassionally timeout. Here, we give it + // 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. final RunResult result = await processUtils.run( @@ -329,6 +330,7 @@ scheme, '-quiet', 'clean', + ...environmentVariablesAsXcodeBuildSettings() ], workingDirectory: fs.currentDirectory.path); } @@ -354,6 +356,21 @@ } } +/// Environment variables prefixed by FLUTTER_XCODE_ will be passed as build configurations to xcodebuild. +/// This allows developers to pass arbitrary build settings in without the tool needing to make a flag +/// for or be aware of each one. This could be used to set code signing build settings in a CI +/// environment without requiring settings changes in the Xcode project. +List<String> environmentVariablesAsXcodeBuildSettings() { + const String xcodeBuildSettingPrefix = 'FLUTTER_XCODE_'; + return platform.environment.entries.where((MapEntry<String, String> mapEntry) { + return mapEntry.key.startsWith(xcodeBuildSettingPrefix); + }).expand<String>((MapEntry<String, String> mapEntry) { + // Remove FLUTTER_XCODE_ prefix from the environment variable to get the build setting. + final String trimmedBuildSettingKey = mapEntry.key.substring(xcodeBuildSettingPrefix.length); + return <String>['$trimmedBuildSettingKey=${mapEntry.value}']; + }).toList(); +} + Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) { final Map<String, String> settings = <String, String>{}; for (Match match in showBuildSettingsOutput.split('\n').map<Match>(_settingExpr.firstMatch)) {
diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 69571bc..d9b16f3 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart
@@ -79,6 +79,7 @@ 'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}', 'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}', 'COMPILER_INDEX_STORE_ENABLE=NO', + ...environmentVariablesAsXcodeBuildSettings() ], trace: true); } finally { status.cancel();
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 b362ca0..bbaf067 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -23,7 +23,7 @@ const String xcodebuild = '/usr/bin/xcodebuild'; void main() { - group('xcodebuild versioning', () { + group('xcodebuild commands', () { mocks.MockProcessManager mockProcessManager; XcodeProjectInterpreter xcodeProjectInterpreter; FakePlatform macOS; @@ -172,6 +172,54 @@ FileSystem: () => fs, ProcessManager: () => mockProcessManager, }); + + testUsingOsxContext('build settings contains Flutter Xcode environment variables', () async { + macOS.environment = Map<String, String>.unmodifiable(<String, String>{ + 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', + 'FLUTTER_XCODE_ARCHS': 'arm64' + }); + when(mockProcessManager.runSync(<String>[ + xcodebuild, + '-project', + macOS.pathSeparator, + '-target', + '', + '-showBuildSettings', + 'CODE_SIGN_STYLE=Manual', + 'ARCHS=arm64' + ], + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))) + .thenReturn(ProcessResult(1, 0, '', '')); + expect(await xcodeProjectInterpreter.getBuildSettings('', ''), const <String, String>{}); + }); + + testUsingOsxContext('clean contains Flutter Xcode environment variables', () async { + macOS.environment = Map<String, String>.unmodifiable(<String, String>{ + 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', + 'FLUTTER_XCODE_ARCHS': 'arm64' + }); + when(mockProcessManager.runSync( + any, + workingDirectory: anyNamed('workingDirectory'))) + .thenReturn(ProcessResult(1, 0, '', '')); + xcodeProjectInterpreter.cleanWorkspace('workspace_path', 'Runner'); + final List<dynamic> captured = verify(mockProcessManager.runSync( + captureAny, + workingDirectory: anyNamed('workingDirectory'), + environment: anyNamed('environment'))).captured; + expect(captured.first, <String>[ + xcodebuild, + '-workspace', + 'workspace_path', + '-scheme', + 'Runner', + '-quiet', + 'clean', + 'CODE_SIGN_STYLE=Manual', + 'ARCHS=arm64' + ]); + }); }); group('xcodebuild -list', () { @@ -338,6 +386,27 @@ }); }); + group('environmentVariablesAsXcodeBuildSettings', () { + FakePlatform platform; + + setUp(() { + platform = fakePlatform('ignored'); + }); + + testUsingContext('environment variables as Xcode build settings', () { + platform.environment = Map<String, String>.unmodifiable(<String, String>{ + 'Ignored': 'Bogus', + 'FLUTTER_NOT_XCODE': 'Bogus', + 'FLUTTER_XCODE_CODE_SIGN_STYLE': 'Manual', + 'FLUTTER_XCODE_ARCHS': 'arm64' + }); + final List<String> environmentVariablesAsBuildSettings = environmentVariablesAsXcodeBuildSettings(); + expect(environmentVariablesAsBuildSettings, <String>['CODE_SIGN_STYLE=Manual', 'ARCHS=arm64']); + }, overrides: <Type, Generator>{ + Platform: () => platform + }); + }); + group('updateGeneratedXcodeProperties', () { MockLocalEngineArtifacts mockArtifacts; MockProcessManager mockProcessManager;