Ian Hickson | 449f4a6 | 2019-11-27 15:04:02 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
Chinmay Garde | 5cebf70 | 2016-02-16 15:01:48 -0800 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 5 | import 'package:file/memory.dart'; |
xster | 837f101 | 2017-05-04 12:26:58 -0700 | [diff] [blame] | 6 | import 'package:meta/meta.dart'; |
Jonah Williams | 021311e | 2021-02-08 09:21:46 -0800 | [diff] [blame] | 7 | import 'package:process/process.dart'; |
xster | 837f101 | 2017-05-04 12:26:58 -0700 | [diff] [blame] | 8 | |
stuartmorgan | 72888c7 | 2019-09-12 16:03:02 -0700 | [diff] [blame] | 9 | import '../base/common.dart'; |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 10 | import '../base/file_system.dart'; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 11 | import '../base/io.dart'; |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 12 | import '../base/logger.dart'; |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 13 | import '../base/os.dart'; |
Zachary Anderson | 6f0ed5e | 2020-05-06 08:15:39 -0700 | [diff] [blame] | 14 | import '../base/platform.dart'; |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 15 | import '../base/process.dart'; |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 16 | import '../base/terminal.dart'; |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 17 | import '../base/utils.dart'; |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 18 | import '../base/version.dart'; |
Jason Simmons | a590ee2 | 2016-05-12 12:22:15 -0700 | [diff] [blame] | 19 | import '../build_info.dart'; |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 20 | import '../reporting/reporting.dart'; |
Chinmay Garde | 5cebf70 | 2016-02-16 15:01:48 -0800 | [diff] [blame] | 21 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 22 | final RegExp _settingExpr = RegExp(r'(\w+)\s*=\s*(.*)$'); |
| 23 | final RegExp _varExpr = RegExp(r'\$\(([^)]*)\)'); |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 24 | |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 25 | /// Interpreter of Xcode projects. |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 26 | class XcodeProjectInterpreter { |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 27 | factory XcodeProjectInterpreter({ |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 28 | required Platform platform, |
| 29 | required ProcessManager processManager, |
| 30 | required Logger logger, |
| 31 | required FileSystem fileSystem, |
| 32 | required Usage usage, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 33 | }) { |
| 34 | return XcodeProjectInterpreter._( |
| 35 | platform: platform, |
| 36 | processManager: processManager, |
| 37 | logger: logger, |
| 38 | fileSystem: fileSystem, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 39 | usage: usage, |
| 40 | ); |
| 41 | } |
| 42 | |
| 43 | XcodeProjectInterpreter._({ |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 44 | required Platform platform, |
| 45 | required ProcessManager processManager, |
| 46 | required Logger logger, |
| 47 | required FileSystem fileSystem, |
| 48 | required Usage usage, |
| 49 | Version? version, |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 50 | }) : _platform = platform, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 51 | _fileSystem = fileSystem, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 52 | _logger = logger, |
| 53 | _processUtils = ProcessUtils(logger: logger, processManager: processManager), |
| 54 | _operatingSystemUtils = OperatingSystemUtils( |
| 55 | fileSystem: fileSystem, |
| 56 | logger: logger, |
| 57 | platform: platform, |
| 58 | processManager: processManager, |
| 59 | ), |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 60 | _version = version, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 61 | _usage = usage; |
| 62 | |
| 63 | /// Create an [XcodeProjectInterpreter] for testing. |
| 64 | /// |
| 65 | /// Defaults to installed with sufficient version, |
| 66 | /// a memory file system, fake platform, buffer logger, |
| 67 | /// test [Usage], and test [Terminal]. |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 68 | /// Set [version] to null to simulate Xcode not being installed. |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 69 | factory XcodeProjectInterpreter.test({ |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 70 | required ProcessManager processManager, |
| 71 | Version? version = const Version.withText(1000, 0, 0, '1000.0.0'), |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 72 | }) { |
| 73 | final Platform platform = FakePlatform( |
| 74 | operatingSystem: 'macos', |
| 75 | environment: <String, String>{}, |
| 76 | ); |
| 77 | return XcodeProjectInterpreter._( |
| 78 | fileSystem: MemoryFileSystem.test(), |
| 79 | platform: platform, |
| 80 | processManager: processManager, |
Jonah Williams | b926c7b | 2021-01-28 12:41:14 -0800 | [diff] [blame] | 81 | usage: TestUsage(), |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 82 | logger: BufferLogger.test(), |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 83 | version: version, |
Jenn Magder | 675fd55 | 2021-01-27 11:04:03 -0800 | [diff] [blame] | 84 | ); |
| 85 | } |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 86 | |
| 87 | final Platform _platform; |
| 88 | final FileSystem _fileSystem; |
| 89 | final ProcessUtils _processUtils; |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 90 | final OperatingSystemUtils _operatingSystemUtils; |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 91 | final Logger _logger; |
Jenn Magder | 77ea848 | 2020-04-02 10:57:37 -0700 | [diff] [blame] | 92 | final Usage _usage; |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 93 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 94 | static final RegExp _versionRegex = RegExp(r'Xcode ([0-9.]+)'); |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 95 | |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 96 | void _updateVersion() { |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 97 | if (!_platform.isMacOS || !_fileSystem.file('/usr/bin/xcodebuild').existsSync()) { |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 98 | return; |
| 99 | } |
| 100 | try { |
Jenn Magder | 4fba774 | 2020-07-14 17:16:02 -0700 | [diff] [blame] | 101 | if (_versionText == null) { |
| 102 | final RunResult result = _processUtils.runSync( |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 103 | <String>[...xcrunCommand(), 'xcodebuild', '-version'], |
Jenn Magder | 4fba774 | 2020-07-14 17:16:02 -0700 | [diff] [blame] | 104 | ); |
| 105 | if (result.exitCode != 0) { |
| 106 | return; |
| 107 | } |
| 108 | _versionText = result.stdout.trim().replaceAll('\n', ', '); |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 109 | } |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 110 | final Match? match = _versionRegex.firstMatch(versionText!); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 111 | if (match == null) { |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 112 | return; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 113 | } |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 114 | final String version = match.group(1)!; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 115 | final List<String> components = version.split('.'); |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 116 | final int majorVersion = int.parse(components[0]); |
| 117 | final int minorVersion = components.length < 2 ? 0 : int.parse(components[1]); |
| 118 | final int patchVersion = components.length < 3 ? 0 : int.parse(components[2]); |
| 119 | _version = Version(majorVersion, minorVersion, patchVersion); |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 120 | } on ProcessException { |
Michael Goderbauer | 6d20ff2 | 2019-01-30 08:56:12 -0800 | [diff] [blame] | 121 | // Ignored, leave values null. |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 122 | } |
| 123 | } |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 124 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 125 | bool get isInstalled => version != null; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 126 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 127 | String? _versionText; |
| 128 | String? get versionText { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 129 | if (_versionText == null) { |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 130 | _updateVersion(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 131 | } |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 132 | return _versionText; |
| 133 | } |
| 134 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 135 | Version? _version; |
| 136 | Version? get version { |
| 137 | if (_version == null) { |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 138 | _updateVersion(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 139 | } |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 140 | return _version; |
Jenn Magder | 4fba774 | 2020-07-14 17:16:02 -0700 | [diff] [blame] | 141 | } |
| 142 | |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 143 | /// The `xcrun` Xcode command to run or locate development |
| 144 | /// tools and properties. |
| 145 | /// |
| 146 | /// Returns `xcrun` on x86 macOS. |
| 147 | /// Returns `/usr/bin/arch -arm64e xcrun` on ARM macOS to force Xcode commands |
| 148 | /// to run outside the x86 Rosetta translation, which may cause crashes. |
| 149 | List<String> xcrunCommand() { |
| 150 | final List<String> xcrunCommand = <String>[]; |
| 151 | if (_operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm) { |
| 152 | // Force Xcode commands to run outside Rosetta. |
| 153 | xcrunCommand.addAll(<String>[ |
| 154 | '/usr/bin/arch', |
| 155 | '-arm64e', |
| 156 | ]); |
| 157 | } |
| 158 | xcrunCommand.add('xcrun'); |
| 159 | return xcrunCommand; |
| 160 | } |
| 161 | |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 162 | /// Asynchronously retrieve xcode build settings. This one is preferred for |
| 163 | /// new call-sites. |
Jenn Magder | 9c87b32 | 2020-05-11 11:56:44 -0700 | [diff] [blame] | 164 | /// |
| 165 | /// If [scheme] is null, xcodebuild will return build settings for the first discovered |
| 166 | /// target (by default this is Runner). |
Zachary Anderson | 8a33d24 | 2019-09-16 07:51:50 -0700 | [diff] [blame] | 167 | Future<Map<String, String>> getBuildSettings( |
Jenn Magder | 9c87b32 | 2020-05-11 11:56:44 -0700 | [diff] [blame] | 168 | String projectPath, { |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 169 | required XcodeProjectBuildContext buildContext, |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 170 | Duration timeout = const Duration(minutes: 1), |
| 171 | }) async { |
Jonah Williams | 4807f80 | 2021-03-16 16:46:21 -0700 | [diff] [blame] | 172 | final Status status = _logger.startSpinner(); |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 173 | final String? scheme = buildContext.scheme; |
| 174 | final String? configuration = buildContext.configuration; |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 175 | final List<String> showBuildSettingsCommand = <String>[ |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 176 | ...xcrunCommand(), |
| 177 | 'xcodebuild', |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 178 | '-project', |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 179 | _fileSystem.path.absolute(projectPath), |
Jenn Magder | 9c87b32 | 2020-05-11 11:56:44 -0700 | [diff] [blame] | 180 | if (scheme != null) |
| 181 | ...<String>['-scheme', scheme], |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 182 | if (configuration != null) |
| 183 | ...<String>['-configuration', configuration], |
| 184 | if (buildContext.environmentType == EnvironmentType.simulator) |
| 185 | ...<String>['-sdk', 'iphonesimulator'], |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 186 | '-showBuildSettings', |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 187 | 'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}', |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 188 | ...environmentVariablesAsXcodeBuildSettings(_platform) |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 189 | ]; |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 190 | try { |
Jenn Magder | 01dc19b | 2019-10-28 19:58:06 -0700 | [diff] [blame] | 191 | // showBuildSettings is reported to occasionally timeout. Here, we give it |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 192 | // a lot of wiggle room (locally on Flutter Gallery, this takes ~1s). |
| 193 | // When there is a timeout, we retry once. |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 194 | final RunResult result = await _processUtils.run( |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 195 | showBuildSettingsCommand, |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 196 | throwOnError: true, |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 197 | workingDirectory: projectPath, |
| 198 | timeout: timeout, |
| 199 | timeoutRetries: 1, |
| 200 | ); |
| 201 | final String out = result.stdout.trim(); |
| 202 | return parseXcodeBuildSettings(out); |
Zachary Anderson | 6c408a0 | 2020-03-06 10:22:12 -0800 | [diff] [blame] | 203 | } on Exception catch (error) { |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 204 | if (error is ProcessException && error.toString().contains('timed out')) { |
| 205 | BuildEvent('xcode-show-build-settings-timeout', |
Jenn Magder | 23404df | 2021-04-12 16:45:03 -0700 | [diff] [blame] | 206 | type: 'ios', |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 207 | command: showBuildSettingsCommand.join(' '), |
Jenn Magder | 77ea848 | 2020-04-02 10:57:37 -0700 | [diff] [blame] | 208 | flutterUsage: _usage, |
Zachary Anderson | 0f2af97 | 2019-09-05 09:50:46 -0700 | [diff] [blame] | 209 | ).send(); |
| 210 | } |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 211 | _logger.printTrace('Unexpected failure to get Xcode build settings: $error.'); |
Zachary Anderson | b7c714e | 2019-08-28 10:03:53 -0700 | [diff] [blame] | 212 | return const <String, String>{}; |
| 213 | } finally { |
| 214 | status.stop(); |
| 215 | } |
| 216 | } |
| 217 | |
Jenn Magder | a61bff2 | 2020-03-19 09:51:01 -0700 | [diff] [blame] | 218 | Future<void> cleanWorkspace(String workspacePath, String scheme, { bool verbose = false }) async { |
Jenn Magder | 4fb9ce8 | 2020-02-27 12:18:06 -0800 | [diff] [blame] | 219 | await _processUtils.run(<String>[ |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 220 | ...xcrunCommand(), |
| 221 | 'xcodebuild', |
Jenn Magder | 892d62f | 2019-08-21 18:42:56 -0700 | [diff] [blame] | 222 | '-workspace', |
| 223 | workspacePath, |
| 224 | '-scheme', |
| 225 | scheme, |
Jenn Magder | a61bff2 | 2020-03-19 09:51:01 -0700 | [diff] [blame] | 226 | if (!verbose) |
| 227 | '-quiet', |
Alexandre Ardhuin | 89427d6 | 2019-09-24 08:06:09 +0200 | [diff] [blame] | 228 | 'clean', |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 229 | ...environmentVariablesAsXcodeBuildSettings(_platform) |
| 230 | ], workingDirectory: _fileSystem.currentDirectory.path); |
Jenn Magder | 892d62f | 2019-08-21 18:42:56 -0700 | [diff] [blame] | 231 | } |
| 232 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 233 | Future<XcodeProjectInfo> getInfo(String projectPath, {String? projectFilename}) async { |
stuartmorgan | 72888c7 | 2019-09-12 16:03:02 -0700 | [diff] [blame] | 234 | // The exit code returned by 'xcodebuild -list' when either: |
| 235 | // * -project is passed and the given project isn't there, or |
| 236 | // * no -project is passed and there isn't a project. |
| 237 | const int missingProjectExitCode = 66; |
Jenn Magder | 8334fb0 | 2021-05-13 21:34:04 -0700 | [diff] [blame] | 238 | // The exit code returned by 'xcodebuild -list' when the project is corrupted. |
| 239 | const int corruptedProjectExitCode = 74; |
| 240 | bool _allowedFailures(int c) => c == missingProjectExitCode || c == corruptedProjectExitCode; |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 241 | final RunResult result = await _processUtils.run( |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 242 | <String>[ |
Jenn Magder | 2f3cccc | 2020-11-04 19:54:02 -0800 | [diff] [blame] | 243 | ...xcrunCommand(), |
| 244 | 'xcodebuild', |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 245 | '-list', |
| 246 | if (projectFilename != null) ...<String>['-project', projectFilename], |
| 247 | ], |
| 248 | throwOnError: true, |
Jenn Magder | 8334fb0 | 2021-05-13 21:34:04 -0700 | [diff] [blame] | 249 | allowedFailures: _allowedFailures, |
Zachary Anderson | 73c10e8 | 2019-09-11 18:20:42 -0700 | [diff] [blame] | 250 | workingDirectory: projectPath, |
| 251 | ); |
Jenn Magder | 8334fb0 | 2021-05-13 21:34:04 -0700 | [diff] [blame] | 252 | if (_allowedFailures(result.exitCode)) { |
| 253 | // User configuration error, tool exit instead of crashing. |
stuartmorgan | 72888c7 | 2019-09-12 16:03:02 -0700 | [diff] [blame] | 254 | throwToolExit('Unable to get Xcode project information:\n ${result.stderr}'); |
| 255 | } |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 256 | return XcodeProjectInfo.fromXcodeBuildOutput(result.toString(), _logger); |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 257 | } |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 258 | } |
| 259 | |
Jenn Magder | 01dc19b | 2019-10-28 19:58:06 -0700 | [diff] [blame] | 260 | /// Environment variables prefixed by FLUTTER_XCODE_ will be passed as build configurations to xcodebuild. |
| 261 | /// This allows developers to pass arbitrary build settings in without the tool needing to make a flag |
| 262 | /// for or be aware of each one. This could be used to set code signing build settings in a CI |
| 263 | /// environment without requiring settings changes in the Xcode project. |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 264 | List<String> environmentVariablesAsXcodeBuildSettings(Platform platform) { |
Jenn Magder | 01dc19b | 2019-10-28 19:58:06 -0700 | [diff] [blame] | 265 | const String xcodeBuildSettingPrefix = 'FLUTTER_XCODE_'; |
Jonah Williams | ef15eac | 2020-01-23 15:03:04 -0800 | [diff] [blame] | 266 | return platform.environment.entries.where((MapEntry<String, String> mapEntry) { |
Jenn Magder | 01dc19b | 2019-10-28 19:58:06 -0700 | [diff] [blame] | 267 | return mapEntry.key.startsWith(xcodeBuildSettingPrefix); |
| 268 | }).expand<String>((MapEntry<String, String> mapEntry) { |
| 269 | // Remove FLUTTER_XCODE_ prefix from the environment variable to get the build setting. |
| 270 | final String trimmedBuildSettingKey = mapEntry.key.substring(xcodeBuildSettingPrefix.length); |
| 271 | return <String>['$trimmedBuildSettingKey=${mapEntry.value}']; |
| 272 | }).toList(); |
| 273 | } |
| 274 | |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 275 | Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) { |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 276 | final Map<String, String> settings = <String, String>{}; |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 277 | for (final Match? match in showBuildSettingsOutput.split('\n').map<Match?>(_settingExpr.firstMatch)) { |
Mikkel Nygaard Ravn | 8507b72 | 2018-02-13 22:19:03 +0100 | [diff] [blame] | 278 | if (match != null) { |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 279 | settings[match[1]!] = match[2]!; |
Mikkel Nygaard Ravn | 8507b72 | 2018-02-13 22:19:03 +0100 | [diff] [blame] | 280 | } |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 281 | } |
| 282 | return settings; |
| 283 | } |
| 284 | |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 285 | /// Substitutes variables in [str] with their values from the specified Xcode |
| 286 | /// project and target. |
xster | b232a84 | 2017-05-15 12:54:32 -0700 | [diff] [blame] | 287 | String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettings) { |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 288 | final Iterable<Match> matches = _varExpr.allMatches(str); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 289 | if (matches.isEmpty) { |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 290 | return str; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 291 | } |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 292 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 293 | return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!); |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 294 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 295 | |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 296 | @immutable |
| 297 | class XcodeProjectBuildContext { |
| 298 | const XcodeProjectBuildContext({this.scheme, this.configuration, this.environmentType = EnvironmentType.physical}); |
| 299 | |
| 300 | final String? scheme; |
| 301 | final String? configuration; |
| 302 | final EnvironmentType environmentType; |
| 303 | |
| 304 | @override |
Dan Field | e36e62e | 2021-06-30 09:46:54 -0700 | [diff] [blame^] | 305 | int get hashCode => Object.hash(scheme, configuration, environmentType); |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 306 | |
| 307 | @override |
| 308 | bool operator ==(Object other) { |
| 309 | if (identical(other, this)) { |
| 310 | return true; |
| 311 | } |
| 312 | return other is XcodeProjectBuildContext && |
| 313 | other.scheme == scheme && |
| 314 | other.configuration == configuration && |
| 315 | other.environmentType == environmentType; |
| 316 | } |
| 317 | } |
| 318 | |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 319 | /// Information about an Xcode project. |
| 320 | /// |
| 321 | /// Represents the output of `xcodebuild -list`. |
| 322 | class XcodeProjectInfo { |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 323 | XcodeProjectInfo( |
| 324 | this.targets, |
| 325 | this.buildConfigurations, |
| 326 | this.schemes, |
| 327 | Logger logger |
| 328 | ) : _logger = logger; |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 329 | |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 330 | factory XcodeProjectInfo.fromXcodeBuildOutput(String output, Logger logger) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 331 | final List<String> targets = <String>[]; |
| 332 | final List<String> buildConfigurations = <String>[]; |
| 333 | final List<String> schemes = <String>[]; |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 334 | List<String>? collector; |
Alexandre Ardhuin | 4f9b6cf | 2020-01-07 16:32:04 +0100 | [diff] [blame] | 335 | for (final String line in output.split('\n')) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 336 | if (line.isEmpty) { |
| 337 | collector = null; |
| 338 | continue; |
| 339 | } else if (line.endsWith('Targets:')) { |
| 340 | collector = targets; |
| 341 | continue; |
| 342 | } else if (line.endsWith('Build Configurations:')) { |
| 343 | collector = buildConfigurations; |
| 344 | continue; |
| 345 | } else if (line.endsWith('Schemes:')) { |
| 346 | collector = schemes; |
| 347 | continue; |
| 348 | } |
| 349 | collector?.add(line.trim()); |
| 350 | } |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 351 | if (schemes.isEmpty) { |
Mikkel Nygaard Ravn | 32ab3db | 2017-08-23 11:58:21 +0200 | [diff] [blame] | 352 | schemes.add('Runner'); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 353 | } |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 354 | return XcodeProjectInfo(targets, buildConfigurations, schemes, logger); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 355 | } |
| 356 | |
| 357 | final List<String> targets; |
| 358 | final List<String> buildConfigurations; |
| 359 | final List<String> schemes; |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 360 | final Logger _logger; |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 361 | |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 362 | bool get definesCustomSchemes => !(schemes.contains('Runner') && schemes.length == 1); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 363 | |
| 364 | /// The expected scheme for [buildInfo]. |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 365 | @visibleForTesting |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 366 | static String expectedSchemeFor(BuildInfo? buildInfo) { |
Jenn Magder | 9c87b32 | 2020-05-11 11:56:44 -0700 | [diff] [blame] | 367 | return toTitleCase(buildInfo?.flavor ?? 'runner'); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 368 | } |
| 369 | |
| 370 | /// The expected build configuration for [buildInfo] and [scheme]. |
| 371 | static String expectedBuildConfigurationFor(BuildInfo buildInfo, String scheme) { |
| 372 | final String baseConfiguration = _baseConfigurationFor(buildInfo); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 373 | if (buildInfo.flavor == null) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 374 | return baseConfiguration; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 375 | } |
Alexandre Ardhuin | 34059ee | 2021-06-01 20:14:06 +0200 | [diff] [blame] | 376 | return '$baseConfiguration-$scheme'; |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 377 | } |
| 378 | |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 379 | /// Checks whether the [buildConfigurations] contains the specified string, without |
| 380 | /// regard to case. |
Jonah Williams | 08576cb | 2020-10-12 09:31:02 -0700 | [diff] [blame] | 381 | bool hasBuildConfigurationForBuildMode(String buildMode) { |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 382 | buildMode = buildMode.toLowerCase(); |
Alexandre Ardhuin | 4f9b6cf | 2020-01-07 16:32:04 +0100 | [diff] [blame] | 383 | for (final String name in buildConfigurations) { |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 384 | if (name.toLowerCase() == buildMode) { |
| 385 | return true; |
| 386 | } |
| 387 | } |
| 388 | return false; |
| 389 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 390 | /// Returns unique scheme matching [buildInfo], or null, if there is no unique |
| 391 | /// best match. |
Jenn Magder | cbc7ce0 | 2021-05-26 16:04:03 -0700 | [diff] [blame] | 392 | String? schemeFor(BuildInfo? buildInfo) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 393 | final String expectedScheme = expectedSchemeFor(buildInfo); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 394 | if (schemes.contains(expectedScheme)) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 395 | return expectedScheme; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 396 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 397 | return _uniqueMatch(schemes, (String candidate) { |
| 398 | return candidate.toLowerCase() == expectedScheme.toLowerCase(); |
| 399 | }); |
| 400 | } |
| 401 | |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 402 | Never reportFlavorNotFoundAndExit() { |
Jenn Magder | e110ca7 | 2020-07-09 16:56:02 -0700 | [diff] [blame] | 403 | _logger.printError(''); |
| 404 | if (definesCustomSchemes) { |
| 405 | _logger.printError('The Xcode project defines schemes: ${schemes.join(', ')}'); |
| 406 | throwToolExit('You must specify a --flavor option to select one of the available schemes.'); |
| 407 | } else { |
| 408 | throwToolExit('The Xcode project does not define custom schemes. You cannot use the --flavor option.'); |
| 409 | } |
| 410 | } |
| 411 | |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 412 | /// Returns unique build configuration matching [buildInfo] and [scheme], or |
| 413 | /// null, if there is no unique best match. |
Jenn Magder | db3f49b | 2021-04-27 17:20:11 -0700 | [diff] [blame] | 414 | String? buildConfigurationFor(BuildInfo? buildInfo, String scheme) { |
| 415 | if (buildInfo == null) { |
| 416 | return null; |
| 417 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 418 | final String expectedConfiguration = expectedBuildConfigurationFor(buildInfo, scheme); |
Jonah Williams | 08576cb | 2020-10-12 09:31:02 -0700 | [diff] [blame] | 419 | if (hasBuildConfigurationForBuildMode(expectedConfiguration)) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 420 | return expectedConfiguration; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 421 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 422 | final String baseConfiguration = _baseConfigurationFor(buildInfo); |
| 423 | return _uniqueMatch(buildConfigurations, (String candidate) { |
| 424 | candidate = candidate.toLowerCase(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 425 | if (buildInfo.flavor == null) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 426 | return candidate == expectedConfiguration.toLowerCase(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 427 | } |
| 428 | return candidate.contains(baseConfiguration.toLowerCase()) && candidate.contains(scheme.toLowerCase()); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 429 | }); |
| 430 | } |
| 431 | |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 432 | static String _baseConfigurationFor(BuildInfo buildInfo) { |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 433 | if (buildInfo.isDebug) { |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 434 | return 'Debug'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 435 | } |
| 436 | if (buildInfo.isProfile) { |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 437 | return 'Profile'; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 438 | } |
Dan Field | fd6b2e1 | 2018-10-31 16:37:53 -0700 | [diff] [blame] | 439 | return 'Release'; |
| 440 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 441 | |
Jenn Magder | 72976f5 | 2021-04-16 17:22:46 -0700 | [diff] [blame] | 442 | static String? _uniqueMatch(Iterable<String> strings, bool Function(String s) matches) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 443 | final List<String> options = strings.where(matches).toList(); |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 444 | if (options.length == 1) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 445 | return options.first; |
Zachary Anderson | e2340c6 | 2019-09-13 14:51:35 -0700 | [diff] [blame] | 446 | } |
| 447 | return null; |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 448 | } |
| 449 | |
| 450 | @override |
| 451 | String toString() { |
| 452 | return 'XcodeProjectInfo($targets, $buildConfigurations, $schemes)'; |
| 453 | } |
| 454 | } |