Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 5 | import 'dart:async'; |
Jason Simmons | 466d154 | 2018-03-12 11:06:32 -0700 | [diff] [blame] | 6 | import 'dart:convert' show json; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 7 | |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 8 | import 'package:meta/meta.dart'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 9 | |
| 10 | import '../application_package.dart'; |
Jakob Andersen | 60c0c3d | 2017-03-10 13:43:57 +0100 | [diff] [blame] | 11 | import '../base/common.dart'; |
Alexandre Ardhuin | 610955f | 2017-04-08 08:43:19 +0200 | [diff] [blame] | 12 | import '../base/context.dart'; |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 13 | import '../base/file_system.dart'; |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 14 | import '../base/fingerprint.dart'; |
Todd Volkert | 016b5ab | 2017-01-09 08:37:00 -0800 | [diff] [blame] | 15 | import '../base/io.dart'; |
Alexandre Ardhuin | 610955f | 2017-04-08 08:43:19 +0200 | [diff] [blame] | 16 | import '../base/logger.dart'; |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 17 | import '../base/os.dart'; |
Todd Volkert | 417c2f2 | 2017-01-25 16:06:41 -0800 | [diff] [blame] | 18 | import '../base/platform.dart'; |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 19 | import '../base/process.dart'; |
Todd Volkert | 60b19b2 | 2016-11-30 08:42:42 -0800 | [diff] [blame] | 20 | import '../base/process_manager.dart'; |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 21 | import '../base/utils.dart'; |
Chinmay Garde | 66fee3a | 2016-05-23 12:58:42 -0700 | [diff] [blame] | 22 | import '../build_info.dart'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 23 | import '../globals.dart'; |
Jakob Andersen | b61e169 | 2017-03-23 14:59:12 +0100 | [diff] [blame] | 24 | import '../plugins.dart'; |
Sigurd Meldgaard | 2d3a5c7 | 2018-07-20 08:00:30 +0200 | [diff] [blame] | 25 | import '../project.dart'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 26 | import '../services.dart'; |
xster | 6a49419 | 2017-07-12 18:35:08 -0700 | [diff] [blame] | 27 | import 'cocoapods.dart'; |
xster | 9d3fb1f | 2017-05-18 11:26:43 -0700 | [diff] [blame] | 28 | import 'code_signing.dart'; |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 29 | import 'xcodeproj.dart'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 30 | |
Chris Bracken | 7fb7852 | 2017-12-15 18:13:10 -0800 | [diff] [blame] | 31 | const int kXcodeRequiredVersionMajor = 9; |
Yegor Jbanov | 23e634a | 2016-03-10 13:08:58 -0800 | [diff] [blame] | 32 | const int kXcodeRequiredVersionMinor = 0; |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 33 | |
Todd Volkert | 8d11f5c | 2018-03-28 10:58:28 -0700 | [diff] [blame] | 34 | IMobileDevice get iMobileDevice => context[IMobileDevice]; |
Chris Bracken | f8c50ea | 2018-09-18 10:05:46 -0700 | [diff] [blame] | 35 | PlistBuddy get plistBuddy => context[PlistBuddy]; |
Todd Volkert | 8d11f5c | 2018-03-28 10:58:28 -0700 | [diff] [blame] | 36 | Xcode get xcode => context[Xcode]; |
Chris Bracken | c04f712 | 2017-06-15 19:24:07 -0700 | [diff] [blame] | 37 | |
Chris Bracken | f8c50ea | 2018-09-18 10:05:46 -0700 | [diff] [blame] | 38 | class PlistBuddy { |
| 39 | const PlistBuddy(); |
| 40 | |
| 41 | static const String path = '/usr/libexec/PlistBuddy'; |
| 42 | |
| 43 | Future<ProcessResult> run(List<String> args) => processManager.run(<String>[path]..addAll(args)); |
| 44 | } |
| 45 | |
| 46 | /// A property list is a key-value representation commonly used for |
| 47 | /// configuration on macOS/iOS systems. |
| 48 | class PropertyList { |
| 49 | const PropertyList(this.plistPath); |
| 50 | |
| 51 | final String plistPath; |
| 52 | |
| 53 | /// Prints the specified key, or returns null if not present. |
| 54 | Future<String> read(String key) async { |
| 55 | final ProcessResult result = await _runCommand('Print $key'); |
| 56 | if (result.exitCode == 0) |
| 57 | return result.stdout.trim(); |
| 58 | return null; |
| 59 | } |
| 60 | |
| 61 | /// Adds [key]. Has no effect if the key already exists. |
| 62 | Future<void> addString(String key, String value) async { |
| 63 | await _runCommand('Add $key string $value'); |
| 64 | } |
| 65 | |
| 66 | /// Updates [key] with the new [value]. Has no effect if the key does not exist. |
| 67 | Future<void> update(String key, String value) async { |
| 68 | await _runCommand('Set $key $value'); |
| 69 | } |
| 70 | |
| 71 | /// Deletes [key]. |
| 72 | Future<void> delete(String key) async { |
| 73 | await _runCommand('Delete $key'); |
| 74 | } |
| 75 | |
| 76 | /// Deletes the content of the property list and creates a new root of the specified type. |
| 77 | Future<void> clearToDict() async { |
| 78 | await _runCommand('Clear dict'); |
| 79 | } |
| 80 | |
| 81 | Future<ProcessResult> _runCommand(String command) async { |
| 82 | return await plistBuddy.run(<String>['-c', command, plistPath]); |
| 83 | } |
| 84 | } |
| 85 | |
Chris Bracken | d6ec71d | 2017-06-15 19:03:24 -0700 | [diff] [blame] | 86 | class IMobileDevice { |
| 87 | const IMobileDevice(); |
| 88 | |
| 89 | bool get isInstalled => exitsHappy(<String>['idevice_id', '-h']); |
| 90 | |
| 91 | /// Returns true if libimobiledevice is installed and working as expected. |
| 92 | /// |
| 93 | /// Older releases of libimobiledevice fail to work with iOS 10.3 and above. |
| 94 | Future<bool> get isWorking async { |
| 95 | if (!isInstalled) |
| 96 | return false; |
| 97 | |
| 98 | // If no device is attached, we're unable to detect any problems. Assume all is well. |
| 99 | final ProcessResult result = (await runAsync(<String>['idevice_id', '-l'])).processResult; |
| 100 | if (result.exitCode != 0 || result.stdout.isEmpty) |
| 101 | return true; |
| 102 | |
| 103 | // Check that we can look up the names of any attached devices. |
| 104 | return await exitsHappyAsync(<String>['idevicename']); |
| 105 | } |
| 106 | |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 107 | Future<String> getAvailableDeviceIDs() async { |
| 108 | try { |
| 109 | final ProcessResult result = await processManager.run(<String>['idevice_id', '-l']); |
| 110 | if (result.exitCode != 0) |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 111 | throw ToolExit('idevice_id returned an error:\n${result.stderr}'); |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 112 | return result.stdout; |
| 113 | } on ProcessException { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 114 | throw ToolExit('Failed to invoke idevice_id. Run flutter doctor.'); |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | |
| 118 | Future<String> getInfoForDevice(String deviceID, String key) async { |
| 119 | try { |
Mihail Slavchev | af0afff | 2017-10-19 01:25:49 +0300 | [diff] [blame] | 120 | final ProcessResult result = await processManager.run(<String>['ideviceinfo', '-u', deviceID, '-k', key, '--simple']); |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 121 | if (result.exitCode != 0) |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 122 | throw ToolExit('idevice_id returned an error:\n${result.stderr}'); |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 123 | return result.stdout.trim(); |
| 124 | } on ProcessException { |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 125 | throw ToolExit('Failed to invoke idevice_id. Run flutter doctor.'); |
Chris Bracken | eba6ceb | 2017-09-01 10:10:49 -0700 | [diff] [blame] | 126 | } |
| 127 | } |
| 128 | |
Chris Bracken | 6650213 | 2017-06-16 14:33:49 -0700 | [diff] [blame] | 129 | /// Starts `idevicesyslog` and returns the running process. |
| 130 | Future<Process> startLogger() => runCommand(<String>['idevicesyslog']); |
| 131 | |
Greg Spencer | 0259be9 | 2017-11-17 10:05:21 -0800 | [diff] [blame] | 132 | /// Captures a screenshot to the specified outputFile. |
Keerti Parthasarathy | aecb7d9 | 2018-06-12 09:30:10 -0700 | [diff] [blame] | 133 | Future<void> takeScreenshot(File outputFile) { |
Chris Bracken | 6650213 | 2017-06-16 14:33:49 -0700 | [diff] [blame] | 134 | return runCheckedAsync(<String>['idevicescreenshot', outputFile.path]); |
| 135 | } |
Chris Bracken | d6ec71d | 2017-06-15 19:03:24 -0700 | [diff] [blame] | 136 | } |
| 137 | |
Chris Bracken | 5d2a460 | 2017-02-01 16:16:33 -0800 | [diff] [blame] | 138 | class Xcode { |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 139 | bool get isInstalledAndMeetsVersionCheck => isInstalled && isVersionSatisfactory; |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 140 | |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 141 | String _xcodeSelectPath; |
Chris Bracken | 845c1b7 | 2017-06-23 16:58:14 -0700 | [diff] [blame] | 142 | String get xcodeSelectPath { |
| 143 | if (_xcodeSelectPath == null) { |
| 144 | try { |
Chris Bracken | 2ebb9e5 | 2017-06-23 18:50:27 -0700 | [diff] [blame] | 145 | _xcodeSelectPath = processManager.runSync(<String>['/usr/bin/xcode-select', '--print-path']).stdout.trim(); |
Chris Bracken | 845c1b7 | 2017-06-23 16:58:14 -0700 | [diff] [blame] | 146 | } on ProcessException { |
| 147 | // Ignore: return null below. |
| 148 | } |
| 149 | } |
| 150 | return _xcodeSelectPath; |
| 151 | } |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 152 | |
Chris Bracken | 845c1b7 | 2017-06-23 16:58:14 -0700 | [diff] [blame] | 153 | bool get isInstalled { |
| 154 | if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) |
| 155 | return false; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 156 | return xcodeProjectInterpreter.isInstalled; |
Chris Bracken | 845c1b7 | 2017-06-23 16:58:14 -0700 | [diff] [blame] | 157 | } |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 158 | |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 159 | int get majorVersion => xcodeProjectInterpreter.majorVersion; |
| 160 | |
| 161 | int get minorVersion => xcodeProjectInterpreter.minorVersion; |
| 162 | |
| 163 | String get versionText => xcodeProjectInterpreter.versionText; |
| 164 | |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 165 | bool _eulaSigned; |
Devon Carew | 16f9e38 | 2016-02-21 00:41:14 -0800 | [diff] [blame] | 166 | /// Has the EULA been signed? |
Chris Bracken | 845c1b7 | 2017-06-23 16:58:14 -0700 | [diff] [blame] | 167 | bool get eulaSigned { |
| 168 | if (_eulaSigned == null) { |
| 169 | try { |
| 170 | final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']); |
| 171 | if (result.stdout != null && result.stdout.contains('license')) |
| 172 | _eulaSigned = false; |
| 173 | else if (result.stderr != null && result.stderr.contains('license')) |
| 174 | _eulaSigned = false; |
| 175 | else |
| 176 | _eulaSigned = true; |
| 177 | } on ProcessException { |
| 178 | _eulaSigned = false; |
| 179 | } |
| 180 | } |
| 181 | return _eulaSigned; |
| 182 | } |
Devon Carew | 16f9e38 | 2016-02-21 00:41:14 -0800 | [diff] [blame] | 183 | |
Jonah Williams | 298f4ef | 2018-03-17 10:57:51 -0700 | [diff] [blame] | 184 | bool _isSimctlInstalled; |
| 185 | |
| 186 | /// Verifies that simctl is installed by trying to run it. |
| 187 | bool get isSimctlInstalled { |
| 188 | if (_isSimctlInstalled == null) { |
| 189 | try { |
| 190 | // This command will error if additional components need to be installed in |
| 191 | // xcode 9.2 and above. |
| 192 | final ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'simctl', 'list']); |
Jonah Williams | 91dcfc5 | 2018-03-19 10:53:04 -0700 | [diff] [blame] | 193 | _isSimctlInstalled = result.stderr == null || result.stderr == ''; |
Jonah Williams | 298f4ef | 2018-03-17 10:57:51 -0700 | [diff] [blame] | 194 | } on ProcessException { |
| 195 | _isSimctlInstalled = false; |
| 196 | } |
| 197 | } |
| 198 | return _isSimctlInstalled; |
| 199 | } |
| 200 | |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 201 | bool get isVersionSatisfactory { |
| 202 | if (!xcodeProjectInterpreter.isInstalled) |
Devon Carew | 4ac1868 | 2016-03-28 16:20:43 -0700 | [diff] [blame] | 203 | return false; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 204 | if (majorVersion > kXcodeRequiredVersionMajor) |
| 205 | return true; |
| 206 | if (majorVersion == kXcodeRequiredVersionMajor) |
| 207 | return minorVersion >= kXcodeRequiredVersionMinor; |
| 208 | return false; |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 209 | } |
Chris Bracken | 2689560 | 2018-04-25 20:50:16 -0700 | [diff] [blame] | 210 | |
| 211 | Future<RunResult> cc(List<String> args) { |
| 212 | return runCheckedAsync(<String>['xcrun', 'cc']..addAll(args)); |
| 213 | } |
| 214 | |
| 215 | Future<RunResult> clang(List<String> args) { |
| 216 | return runCheckedAsync(<String>['xcrun', 'clang']..addAll(args)); |
| 217 | } |
Danny Tuppeny | d3f6128 | 2018-07-19 10:32:44 +0100 | [diff] [blame] | 218 | |
| 219 | String getSimulatorPath() { |
| 220 | if (xcodeSelectPath == null) |
| 221 | return null; |
| 222 | final List<String> searchPaths = <String>[ |
| 223 | fs.path.join(xcodeSelectPath, 'Applications', 'Simulator.app'), |
| 224 | ]; |
| 225 | return searchPaths.where((String p) => p != null).firstWhere( |
| 226 | (String p) => fs.directory(p).existsSync(), |
| 227 | orElse: () => null, |
| 228 | ); |
| 229 | } |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 230 | } |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 231 | |
Chris Bracken | f8c50ea | 2018-09-18 10:05:46 -0700 | [diff] [blame] | 232 | /// Sets the Xcode system. |
| 233 | /// |
| 234 | /// Xcode 10 added a new (default) build system with better performance and |
| 235 | /// stricter checks. Flutter apps without plugins build fine under the new |
| 236 | /// system, but it causes build breakages in projects with CocoaPods enabled. |
| 237 | /// This affects Flutter apps with plugins. |
| 238 | /// |
| 239 | /// Once Flutter has been updated to be fully compliant with the new build |
| 240 | /// system, this can be removed. |
| 241 | // |
| 242 | // TODO(cbracken): remove when https://github.com/flutter/flutter/issues/20685 is fixed. |
| 243 | Future<void> setXcodeWorkspaceBuildSystem({ |
| 244 | @required Directory workspaceDirectory, |
| 245 | @required File workspaceSettings, |
| 246 | @required bool modern, |
| 247 | }) async { |
| 248 | // If this isn't a workspace, we're not using CocoaPods and can use the new |
| 249 | // build system. |
| 250 | if (!workspaceDirectory.existsSync()) |
| 251 | return; |
| 252 | |
| 253 | final PropertyList plist = PropertyList(workspaceSettings.path); |
| 254 | if (!workspaceSettings.existsSync()) { |
| 255 | workspaceSettings.parent.createSync(recursive: true); |
| 256 | await plist.clearToDict(); |
| 257 | } |
| 258 | |
| 259 | const String kBuildSystemType = 'BuildSystemType'; |
| 260 | if (modern) { |
| 261 | printTrace('Using new Xcode build system.'); |
| 262 | await plist.delete(kBuildSystemType); |
| 263 | } else { |
| 264 | printTrace('Using legacy Xcode build system.'); |
| 265 | if (await plist.read(kBuildSystemType) == null) { |
| 266 | await plist.addString(kBuildSystemType, 'Original'); |
| 267 | } else { |
| 268 | await plist.update(kBuildSystemType, 'Original'); |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 273 | Future<XcodeBuildResult> buildXcodeProject({ |
Todd Volkert | 904d524 | 2016-10-13 16:17:50 -0700 | [diff] [blame] | 274 | BuildableIOSApp app, |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 275 | BuildInfo buildInfo, |
Simon Lightfoot | be6501a | 2018-05-21 11:54:38 +0100 | [diff] [blame] | 276 | String targetOverride, |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 277 | bool buildForDevice, |
Alexandre Ardhuin | 09276be | 2018-06-05 08:50:40 +0200 | [diff] [blame] | 278 | bool codesign = true, |
| 279 | bool usesTerminalUi = true, |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 280 | }) async { |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 281 | if (!await upgradePbxProjWithFlutterAssets(app.project)) |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 282 | return XcodeBuildResult(success: false); |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 283 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 284 | if (!_checkXcodeVersion()) |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 285 | return XcodeBuildResult(success: false); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 286 | |
Chris Bracken | f8c50ea | 2018-09-18 10:05:46 -0700 | [diff] [blame] | 287 | // TODO(cbracken) remove when https://github.com/flutter/flutter/issues/20685 is fixed. |
| 288 | await setXcodeWorkspaceBuildSystem( |
| 289 | workspaceDirectory: app.project.xcodeWorkspace, |
| 290 | workspaceSettings: app.project.xcodeWorkspaceSharedSettings, |
| 291 | modern: false, |
| 292 | ); |
| 293 | |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 294 | final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 295 | if (!projectInfo.targets.contains('Runner')) { |
| 296 | printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.'); |
| 297 | printError('Open Xcode to fix the problem:'); |
| 298 | printError(' open ios/Runner.xcworkspace'); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 299 | return XcodeBuildResult(success: false); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 300 | } |
| 301 | final String scheme = projectInfo.schemeFor(buildInfo); |
| 302 | if (scheme == null) { |
| 303 | printError(''); |
| 304 | if (projectInfo.definesCustomSchemes) { |
| 305 | printError('The Xcode project defines schemes: ${projectInfo.schemes.join(', ')}'); |
| 306 | printError('You must specify a --flavor option to select one of them.'); |
| 307 | } else { |
| 308 | printError('The Xcode project does not define custom schemes.'); |
| 309 | printError('You cannot use the --flavor option.'); |
| 310 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 311 | return XcodeBuildResult(success: false); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 312 | } |
| 313 | final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme); |
| 314 | if (configuration == null) { |
| 315 | printError(''); |
| 316 | printError('The Xcode project defines build configurations: ${projectInfo.buildConfigurations.join(', ')}'); |
| 317 | printError('Flutter expects a build configuration named ${XcodeProjectInfo.expectedBuildConfigurationFor(buildInfo, scheme)} or similar.'); |
| 318 | printError('Open Xcode to fix the problem:'); |
| 319 | printError(' open ios/Runner.xcworkspace'); |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 320 | return XcodeBuildResult(success: false); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 321 | } |
| 322 | |
xster | 4565395 | 2018-03-28 18:58:06 -0700 | [diff] [blame] | 323 | Map<String, String> autoSigningConfigs; |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 324 | if (codesign && buildForDevice) |
xster | 4565395 | 2018-03-28 18:58:06 -0700 | [diff] [blame] | 325 | autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(iosApp: app, usesTerminalUi: usesTerminalUi); |
xster | b232a84 | 2017-05-15 12:54:32 -0700 | [diff] [blame] | 326 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 327 | // Before the build, all service definitions must be updated and the dylibs |
| 328 | // copied over to a location that is suitable for Xcodebuild to find them. |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 329 | await _addServicesToBundle(app.project.hostAppRoot); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 330 | |
Mikkel Nygaard Ravn | b280074 | 2018-08-02 14:12:25 +0200 | [diff] [blame] | 331 | final FlutterProject project = await FlutterProject.current(); |
Sigurd Meldgaard | 2d3a5c7 | 2018-07-20 08:00:30 +0200 | [diff] [blame] | 332 | await updateGeneratedXcodeProperties( |
| 333 | project: project, |
Simon Lightfoot | be6501a | 2018-05-21 11:54:38 +0100 | [diff] [blame] | 334 | targetOverride: targetOverride, |
Sigurd Meldgaard | 2d3a5c7 | 2018-07-20 08:00:30 +0200 | [diff] [blame] | 335 | buildInfo: buildInfo, |
xster | 837f101 | 2017-05-04 12:26:58 -0700 | [diff] [blame] | 336 | ); |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 337 | refreshPluginsList(project); |
| 338 | if (hasPlugins(project) || (project.isModule && project.ios.podfile.existsSync())) { |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 339 | // If the Xcode project, Podfile, or Generated.xcconfig have changed since |
| 340 | // last run, pods should be updated. |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 341 | final Fingerprinter fingerprinter = Fingerprinter( |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 342 | fingerprintPath: fs.path.join(getIosBuildDirectory(), 'pod_inputs.fingerprint'), |
| 343 | paths: <String>[ |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 344 | app.project.xcodeProjectInfoFile.path, |
| 345 | app.project.podfile.path, |
| 346 | app.project.generatedXcodePropertiesFile.path, |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 347 | ], |
| 348 | properties: <String, String>{}, |
| 349 | ); |
| 350 | final bool didPodInstall = await cocoaPods.processPods( |
Sigurd Meldgaard | 2d3a5c7 | 2018-07-20 08:00:30 +0200 | [diff] [blame] | 351 | iosProject: project.ios, |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 352 | iosEngineDir: flutterFrameworkDir(buildInfo.mode), |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 353 | isSwift: project.ios.isSwift, |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 354 | dependenciesChanged: !await fingerprinter.doesFingerprintMatch() |
xster | 4d2c2aa | 2017-12-27 07:30:31 -0800 | [diff] [blame] | 355 | ); |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 356 | if (didPodInstall) |
| 357 | await fingerprinter.writeFingerprint(); |
xster | 4d2c2aa | 2017-12-27 07:30:31 -0800 | [diff] [blame] | 358 | } |
| 359 | |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 360 | final List<String> buildCommands = <String>[ |
Chinmay Garde | 66e1006 | 2016-03-25 15:36:45 -0700 | [diff] [blame] | 361 | '/usr/bin/env', |
| 362 | 'xcrun', |
| 363 | 'xcodebuild', |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 364 | '-configuration', configuration, |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 365 | ]; |
| 366 | |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 367 | if (logger.isVerbose) { |
| 368 | // An environment variable to be passed to xcode_backend.sh determining |
| 369 | // whether to echo back executed commands. |
| 370 | buildCommands.add('VERBOSE_SCRIPT_LOGGING=YES'); |
| 371 | } else { |
| 372 | // This will print warnings and errors only. |
| 373 | buildCommands.add('-quiet'); |
| 374 | } |
| 375 | |
xster | 4565395 | 2018-03-28 18:58:06 -0700 | [diff] [blame] | 376 | if (autoSigningConfigs != null) { |
| 377 | for (MapEntry<String, String> signingConfig in autoSigningConfigs.entries) { |
| 378 | buildCommands.add('${signingConfig.key}=${signingConfig.value}'); |
| 379 | } |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 380 | buildCommands.add('-allowProvisioningUpdates'); |
| 381 | buildCommands.add('-allowProvisioningDeviceRegistration'); |
xster | 6d2dc05 | 2018-02-12 16:44:21 -0800 | [diff] [blame] | 382 | } |
xster | b232a84 | 2017-05-15 12:54:32 -0700 | [diff] [blame] | 383 | |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 384 | final List<FileSystemEntity> contents = app.project.hostAppRoot.listSync(); |
Collin Jackson | 6073a7e | 2016-07-12 17:30:32 -0700 | [diff] [blame] | 385 | for (FileSystemEntity entity in contents) { |
Michael Goderbauer | 5e54fd5 | 2017-02-13 17:45:50 -0800 | [diff] [blame] | 386 | if (fs.path.extension(entity.path) == '.xcworkspace') { |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 387 | buildCommands.addAll(<String>[ |
Michael Goderbauer | 5e54fd5 | 2017-02-13 17:45:50 -0800 | [diff] [blame] | 388 | '-workspace', fs.path.basename(entity.path), |
Mikkel Nygaard Ravn | 32ab3db | 2017-08-23 11:58:21 +0200 | [diff] [blame] | 389 | '-scheme', scheme, |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 390 | 'BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}', |
Collin Jackson | 6073a7e | 2016-07-12 17:30:32 -0700 | [diff] [blame] | 391 | ]); |
| 392 | break; |
| 393 | } |
| 394 | } |
| 395 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 396 | if (buildForDevice) { |
Chris Bracken | 849676f | 2018-05-06 18:43:07 -0700 | [diff] [blame] | 397 | buildCommands.addAll(<String>['-sdk', 'iphoneos']); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 398 | } else { |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 399 | buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 400 | } |
| 401 | |
Todd Volkert | 74e3167 | 2016-11-10 10:30:17 -0800 | [diff] [blame] | 402 | if (!codesign) { |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 403 | buildCommands.addAll( |
Todd Volkert | 74e3167 | 2016-11-10 10:30:17 -0800 | [diff] [blame] | 404 | <String>[ |
| 405 | 'CODE_SIGNING_ALLOWED=NO', |
| 406 | 'CODE_SIGNING_REQUIRED=NO', |
| 407 | 'CODE_SIGNING_IDENTITY=""' |
| 408 | ] |
| 409 | ); |
| 410 | } |
| 411 | |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 412 | Status buildSubStatus; |
| 413 | Status initialBuildStatus; |
Ian Hickson | 3dec6a6 | 2018-08-17 13:17:23 -0700 | [diff] [blame] | 414 | Directory tempDir; |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 415 | |
Greg Spencer | efcd9a8 | 2018-09-20 15:45:48 -0700 | [diff] [blame] | 416 | if (logger.hasTerminal) { |
Ian Hickson | 3dec6a6 | 2018-08-17 13:17:23 -0700 | [diff] [blame] | 417 | tempDir = fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.'); |
| 418 | final File scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout'); |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 419 | os.makePipe(scriptOutputPipeFile.path); |
| 420 | |
| 421 | Future<void> listenToScriptOutputLine() async { |
| 422 | final List<String> lines = await scriptOutputPipeFile.readAsLines(); |
| 423 | for (String line in lines) { |
| 424 | if (line == 'done') { |
| 425 | buildSubStatus?.stop(); |
| 426 | buildSubStatus = null; |
| 427 | } else { |
| 428 | initialBuildStatus.cancel(); |
| 429 | buildSubStatus = logger.startProgress( |
| 430 | line, |
| 431 | expectSlowOperation: true, |
jcollins-g | 614df69 | 2018-02-28 12:09:52 -0800 | [diff] [blame] | 432 | progressIndicatorPadding: kDefaultStatusPadding - 7, |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 433 | ); |
| 434 | } |
| 435 | } |
| 436 | return listenToScriptOutputLine(); |
| 437 | } |
| 438 | |
| 439 | // Trigger the start of the pipe -> stdout loop. Ignore exceptions. |
| 440 | listenToScriptOutputLine(); // ignore: unawaited_futures |
| 441 | |
| 442 | buildCommands.add('SCRIPT_OUTPUT_STREAM_FILE=${scriptOutputPipeFile.absolute.path}'); |
| 443 | } |
| 444 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 445 | final Stopwatch buildStopwatch = Stopwatch()..start(); |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 446 | initialBuildStatus = logger.startProgress('Starting Xcode build...'); |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 447 | final RunResult buildResult = await runAsync( |
| 448 | buildCommands, |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 449 | workingDirectory: app.project.hostAppRoot.path, |
Ian Hickson | 0d21e69 | 2016-06-14 18:16:55 -0700 | [diff] [blame] | 450 | allowReentrantFlutter: true |
| 451 | ); |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 452 | buildSubStatus?.stop(); |
| 453 | initialBuildStatus?.cancel(); |
| 454 | buildStopwatch.stop(); |
| 455 | // Free pipe file. |
Ian Hickson | 3dec6a6 | 2018-08-17 13:17:23 -0700 | [diff] [blame] | 456 | tempDir?.deleteSync(recursive: true); |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 457 | printStatus( |
Greg Spencer | 7caa659 | 2018-09-19 15:22:43 -0700 | [diff] [blame] | 458 | 'Xcode build done.'.padRight(kDefaultStatusPadding + 1) |
xster | 1e397d3 | 2018-02-15 15:16:23 -0800 | [diff] [blame] | 459 | + '${getElapsedAsSeconds(buildStopwatch.elapsed).padLeft(5)}', |
| 460 | ); |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 461 | |
| 462 | // Run -showBuildSettings again but with the exact same parameters as the build. |
| 463 | final Map<String, String> buildSettings = parseXcodeBuildSettings(runCheckedSync( |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 464 | (List<String> |
xster | 80c10bc | 2018-02-13 16:49:01 -0800 | [diff] [blame] | 465 | .from(buildCommands) |
| 466 | ..add('-showBuildSettings')) |
| 467 | // Undocumented behaviour: xcodebuild craps out if -showBuildSettings |
| 468 | // is used together with -allowProvisioningUpdates or |
| 469 | // -allowProvisioningDeviceRegistration and freezes forever. |
| 470 | .where((String buildCommand) { |
| 471 | return !const <String>[ |
| 472 | '-allowProvisioningUpdates', |
| 473 | '-allowProvisioningDeviceRegistration', |
| 474 | ].contains(buildCommand); |
Devon Carew | 9d9836f | 2018-07-09 12:22:46 -0700 | [diff] [blame] | 475 | }).toList(), |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 476 | workingDirectory: app.project.hostAppRoot.path, |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 477 | )); |
| 478 | |
| 479 | if (buildResult.exitCode != 0) { |
xster | 1cc7814 | 2017-02-08 18:27:47 -0800 | [diff] [blame] | 480 | printStatus('Failed to build iOS app'); |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 481 | if (buildResult.stderr.isNotEmpty) { |
xster | 1cc7814 | 2017-02-08 18:27:47 -0800 | [diff] [blame] | 482 | printStatus('Error output from Xcode build:\n↳'); |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 483 | printStatus(buildResult.stderr, indent: 4); |
xster | 1cc7814 | 2017-02-08 18:27:47 -0800 | [diff] [blame] | 484 | } |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 485 | if (buildResult.stdout.isNotEmpty) { |
xster | 1cc7814 | 2017-02-08 18:27:47 -0800 | [diff] [blame] | 486 | printStatus('Xcode\'s output:\n↳'); |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 487 | printStatus(buildResult.stdout, indent: 4); |
xster | 1cc7814 | 2017-02-08 18:27:47 -0800 | [diff] [blame] | 488 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 489 | return XcodeBuildResult( |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 490 | success: false, |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 491 | stdout: buildResult.stdout, |
| 492 | stderr: buildResult.stderr, |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 493 | xcodeBuildExecution: XcodeBuildExecution( |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 494 | buildCommands: buildCommands, |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 495 | appDirectory: app.project.hostAppRoot.path, |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 496 | buildForPhysicalDevice: buildForDevice, |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 497 | buildSettings: buildSettings, |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 498 | ), |
| 499 | ); |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 500 | } else { |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 501 | final String expectedOutputDirectory = fs.path.join( |
| 502 | buildSettings['TARGET_BUILD_DIR'], |
| 503 | buildSettings['WRAPPER_NAME'], |
| 504 | ); |
| 505 | |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 506 | String outputDir; |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 507 | if (fs.isDirectorySync(expectedOutputDirectory)) { |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 508 | // Copy app folder to a place where other tools can find it without knowing |
| 509 | // the BuildInfo. |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 510 | outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/'); |
Alexander Aprelev | a92110a | 2018-01-20 11:11:44 -0800 | [diff] [blame] | 511 | if (fs.isDirectorySync(outputDir)) { |
| 512 | // Previous output directory might have incompatible artifacts |
Chris Bracken | 041ff62 | 2018-09-04 08:50:05 -0700 | [diff] [blame] | 513 | // (for example, kernel binary files produced from previous run). |
Alexander Aprelev | a92110a | 2018-01-20 11:11:44 -0800 | [diff] [blame] | 514 | fs.directory(outputDir).deleteSync(recursive: true); |
| 515 | } |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 516 | copyDirectorySync(fs.directory(expectedOutputDirectory), fs.directory(outputDir)); |
| 517 | } else { |
| 518 | printError('Build succeeded but the expected app at $expectedOutputDirectory not found'); |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 519 | } |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 520 | return XcodeBuildResult(success: true, output: outputDir); |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 521 | } |
| 522 | } |
| 523 | |
xster | 4d2c2aa | 2017-12-27 07:30:31 -0800 | [diff] [blame] | 524 | String readGeneratedXcconfig(String appPath) { |
| 525 | final String generatedXcconfigPath = |
Mikkel Nygaard Ravn | 2000435 | 2018-02-16 10:17:28 +0100 | [diff] [blame] | 526 | fs.path.join(fs.currentDirectory.path, appPath, 'Flutter', 'Generated.xcconfig'); |
xster | 4d2c2aa | 2017-12-27 07:30:31 -0800 | [diff] [blame] | 527 | final File generatedXcconfigFile = fs.file(generatedXcconfigPath); |
| 528 | if (!generatedXcconfigFile.existsSync()) |
| 529 | return null; |
| 530 | return generatedXcconfigFile.readAsStringSync(); |
| 531 | } |
| 532 | |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 533 | Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result) async { |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 534 | if (result.xcodeBuildExecution != null && |
| 535 | result.xcodeBuildExecution.buildForPhysicalDevice && |
xster | d5d2cdf | 2017-11-16 22:53:22 -0800 | [diff] [blame] | 536 | result.stdout?.contains('BCEROR') == true && |
| 537 | // May need updating if Xcode changes its outputs. |
| 538 | result.stdout?.contains('Xcode couldn\'t find a provisioning profile matching') == true) { |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 539 | printError(noProvisioningProfileInstruction, emphasis: true); |
| 540 | return; |
| 541 | } |
Chris Bracken | 024cebf | 2018-02-05 18:11:07 -0800 | [diff] [blame] | 542 | // Make sure the user has specified one of: |
| 543 | // * DEVELOPMENT_TEAM (automatic signing) |
| 544 | // * PROVISIONING_PROFILE (manual signing) |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 545 | if (result.xcodeBuildExecution != null && |
| 546 | result.xcodeBuildExecution.buildForPhysicalDevice && |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 547 | !<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any( |
| 548 | result.xcodeBuildExecution.buildSettings.containsKey) |
| 549 | ) { |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 550 | printError(noDevelopmentTeamInstruction, emphasis: true); |
| 551 | return; |
| 552 | } |
Mikkel Nygaard Ravn | 9496e6d | 2017-08-23 10:55:35 +0200 | [diff] [blame] | 553 | if (result.xcodeBuildExecution != null && |
| 554 | result.xcodeBuildExecution.buildForPhysicalDevice && |
Yegor | 85473d0 | 2018-04-19 18:29:49 -0700 | [diff] [blame] | 555 | result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER']?.contains('com.example') == true) { |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 556 | printError(''); |
| 557 | printError('It appears that your application still contains the default signing identifier.'); |
Mikkel Nygaard Ravn | 4353297 | 2018-01-18 09:21:24 +0100 | [diff] [blame] | 558 | printError("Try replacing 'com.example' with your signing id in Xcode:"); |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 559 | printError(' open ios/Runner.xcworkspace'); |
| 560 | return; |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 561 | } |
| 562 | if (result.stdout?.contains('Code Sign error') == true) { |
| 563 | printError(''); |
| 564 | printError('It appears that there was a problem signing your application prior to installation on the device.'); |
| 565 | printError(''); |
xster | c2b0a30 | 2017-06-07 15:56:13 -0700 | [diff] [blame] | 566 | printError('Verify that the Bundle Identifier in your project is your signing id in Xcode'); |
| 567 | printError(' open ios/Runner.xcworkspace'); |
| 568 | printError(''); |
| 569 | printError("Also try selecting 'Product > Build' to fix the problem:"); |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 570 | return; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 571 | } |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 572 | } |
Devon Carew | f68d86d | 2016-03-03 15:10:45 -0800 | [diff] [blame] | 573 | |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 574 | class XcodeBuildResult { |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 575 | XcodeBuildResult( |
| 576 | { |
| 577 | @required this.success, |
| 578 | this.output, |
| 579 | this.stdout, |
| 580 | this.stderr, |
| 581 | this.xcodeBuildExecution, |
| 582 | } |
| 583 | ); |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 584 | |
| 585 | final bool success; |
| 586 | final String output; |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 587 | final String stdout; |
| 588 | final String stderr; |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 589 | /// The invocation of the build that resulted in this result instance. |
| 590 | final XcodeBuildExecution xcodeBuildExecution; |
| 591 | } |
| 592 | |
| 593 | /// Describes an invocation of a Xcode build command. |
| 594 | class XcodeBuildExecution { |
| 595 | XcodeBuildExecution( |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 596 | { |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 597 | @required this.buildCommands, |
| 598 | @required this.appDirectory, |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 599 | @required this.buildForPhysicalDevice, |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 600 | @required this.buildSettings, |
xster | 0c8e181 | 2017-02-07 16:59:58 -0800 | [diff] [blame] | 601 | } |
| 602 | ); |
| 603 | |
| 604 | /// The original list of Xcode build commands used to produce this build result. |
| 605 | final List<String> buildCommands; |
| 606 | final String appDirectory; |
| 607 | final bool buildForPhysicalDevice; |
xster | d401bd7 | 2018-02-13 01:56:13 -0800 | [diff] [blame] | 608 | /// The build settings corresponding to the [buildCommands] invocation. |
| 609 | final Map<String, String> buildSettings; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 610 | } |
| 611 | |
Alexandre Ardhuin | 7667db6 | 2018-03-14 06:24:49 +0100 | [diff] [blame] | 612 | const String _xcodeRequirement = 'Xcode $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor or greater is required to develop for iOS.'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 613 | |
| 614 | bool _checkXcodeVersion() { |
Todd Volkert | 417c2f2 | 2017-01-25 16:06:41 -0800 | [diff] [blame] | 615 | if (!platform.isMacOS) |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 616 | return false; |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 617 | if (!xcodeProjectInterpreter.isInstalled) { |
Jakob Andersen | 60c0c3d | 2017-03-10 13:43:57 +0100 | [diff] [blame] | 618 | printError('Cannot find "xcodebuild". $_xcodeRequirement'); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 619 | return false; |
| 620 | } |
Mikkel Nygaard Ravn | 0d59679 | 2018-03-07 08:41:23 +0100 | [diff] [blame] | 621 | if (!xcode.isVersionSatisfactory) { |
| 622 | printError('Found "${xcodeProjectInterpreter.versionText}". $_xcodeRequirement'); |
| 623 | return false; |
| 624 | } |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 625 | return true; |
| 626 | } |
| 627 | |
Ian Hickson | d745e20 | 2016-03-12 00:32:34 -0800 | [diff] [blame] | 628 | Future<Null> _addServicesToBundle(Directory bundle) async { |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 629 | final List<Map<String, String>> services = <Map<String, String>>[]; |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 630 | printTrace('Trying to resolve native pub services.'); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 631 | |
| 632 | // Step 1: Parse the service configuration yaml files present in the service |
| 633 | // pub packages. |
| 634 | await parseServiceConfigs(services); |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 635 | printTrace('Found ${services.length} service definition(s).'); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 636 | |
| 637 | // Step 2: Copy framework dylibs to the correct spot for xcodebuild to pick up. |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 638 | final Directory frameworksDirectory = fs.directory(fs.path.join(bundle.path, 'Frameworks')); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 639 | await _copyServiceFrameworks(services, frameworksDirectory); |
| 640 | |
| 641 | // Step 3: Copy the service definitions manifest at the correct spot for |
| 642 | // xcodebuild to pick up. |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 643 | final File manifestFile = fs.file(fs.path.join(bundle.path, 'ServiceDefinitions.json')); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 644 | _copyServiceDefinitionsManifest(services, manifestFile); |
| 645 | } |
| 646 | |
Ian Hickson | d745e20 | 2016-03-12 00:32:34 -0800 | [diff] [blame] | 647 | Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Directory frameworksDirectory) async { |
Michael Goderbauer | 5e54fd5 | 2017-02-13 17:45:50 -0800 | [diff] [blame] | 648 | printTrace("Copying service frameworks to '${fs.path.absolute(frameworksDirectory.path)}'."); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 649 | frameworksDirectory.createSync(recursive: true); |
| 650 | for (Map<String, String> service in services) { |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 651 | final String dylibPath = await getServiceFromUrl(service['ios-framework'], service['root'], service['name']); |
| 652 | final File dylib = fs.file(dylibPath); |
Alexandre Ardhuin | 1fce14a | 2017-10-22 18:11:36 +0200 | [diff] [blame] | 653 | printTrace('Copying ${dylib.path} into bundle.'); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 654 | if (!dylib.existsSync()) { |
| 655 | printError("The service dylib '${dylib.path}' does not exist."); |
| 656 | continue; |
| 657 | } |
| 658 | // Shell out so permissions on the dylib are preserved. |
Todd Volkert | 60c5ffc | 2017-04-25 17:23:00 -0700 | [diff] [blame] | 659 | await runCheckedAsync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 660 | } |
| 661 | } |
| 662 | |
| 663 | void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) { |
| 664 | printTrace("Creating service definitions manifest at '${manifest.path}'"); |
Chris Bracken | 7a09316 | 2017-03-03 17:50:46 -0800 | [diff] [blame] | 665 | final List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => <String, String>{ |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 666 | 'name': service['name'], |
| 667 | // Since we have already moved it to the Frameworks directory. Strip away |
| 668 | // the directory and basenames. |
Michael Goderbauer | 5e54fd5 | 2017-02-13 17:45:50 -0800 | [diff] [blame] | 669 | 'framework': fs.path.basenameWithoutExtension(service['ios-framework']) |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 670 | }).toList(); |
Jason Simmons | 466d154 | 2018-03-12 11:06:32 -0700 | [diff] [blame] | 671 | final Map<String, dynamic> jsonObject = <String, dynamic>{ 'services' : jsonServices }; |
Leaf Petersen | 32f9444 | 2018-07-20 15:07:24 -0700 | [diff] [blame] | 672 | manifest.writeAsStringSync(json.encode(jsonObject), mode: FileMode.write, flush: true); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 673 | } |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 674 | |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 675 | Future<bool> upgradePbxProjWithFlutterAssets(IosProject project) async { |
| 676 | final File xcodeProjectFile = project.xcodeProjectInfoFile; |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 677 | assert(await xcodeProjectFile.exists()); |
| 678 | final List<String> lines = await xcodeProjectFile.readAsLines(); |
| 679 | |
Mikkel Nygaard Ravn | a600fe7 | 2018-09-25 21:21:13 +0200 | [diff] [blame^] | 680 | if (lines.any((String line) => line.contains('flutter_assets in Resources'))) |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 681 | return true; |
| 682 | |
Alexandre Ardhuin | 841d5d7 | 2018-02-01 07:51:26 +0100 | [diff] [blame] | 683 | const String l1 = ' 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };'; |
| 684 | const String l2 = ' 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };'; |
| 685 | const String l3 = ' 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };'; |
| 686 | const String l4 = ' 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };'; |
| 687 | const String l5 = ' 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,'; |
| 688 | const String l6 = ' 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,'; |
| 689 | const String l7 = ' 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,'; |
| 690 | const String l8 = ' 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,'; |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 691 | |
| 692 | |
Mikkel Nygaard Ravn | 22832d3 | 2018-08-30 16:18:44 +0200 | [diff] [blame] | 693 | printStatus("Upgrading project.pbxproj of ${project.hostAppBundleName}' to include the " |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 694 | "'flutter_assets' directory"); |
| 695 | |
| 696 | if (!lines.contains(l1) || !lines.contains(l3) || |
| 697 | !lines.contains(l5) || !lines.contains(l7)) { |
| 698 | printError('Automatic upgrade of project.pbxproj failed.'); |
Chris Bracken | cdbdafa | 2018-05-03 19:40:16 -0700 | [diff] [blame] | 699 | printError(' To manually upgrade, open ${xcodeProjectFile.path}:'); |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 700 | printError(' Add the following line in the "PBXBuildFile" section'); |
| 701 | printError(l2); |
| 702 | printError(' Add the following line in the "PBXFileReference" section'); |
| 703 | printError(l4); |
| 704 | printError(' Add the following line in the "children" list of the "Flutter" group in the "PBXGroup" section'); |
| 705 | printError(l6); |
| 706 | printError(' Add the following line in the "files" list of "Resources" in the "PBXResourcesBuildPhase" section'); |
| 707 | printError(l8); |
| 708 | return false; |
| 709 | } |
| 710 | |
| 711 | lines.insert(lines.indexOf(l1) + 1, l2); |
| 712 | lines.insert(lines.indexOf(l3) + 1, l4); |
| 713 | lines.insert(lines.indexOf(l5) + 1, l6); |
| 714 | lines.insert(lines.indexOf(l7) + 1, l8); |
| 715 | |
Alexandre Ardhuin | 841d5d7 | 2018-02-01 07:51:26 +0100 | [diff] [blame] | 716 | const String l9 = ' 9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; };'; |
| 717 | const String l10 = ' 9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = "<group>"; };'; |
| 718 | const String l11 = ' 9740EEB71CF902C7004384FC /* app.flx */,'; |
| 719 | const String l12 = ' 9740EEBB1CF902C7004384FC /* app.flx in Resources */,'; |
Sarah Zakarias | 73552ec | 2017-12-18 18:58:44 +0100 | [diff] [blame] | 720 | |
| 721 | if (lines.contains(l9)) { |
| 722 | printStatus('Removing app.flx from project.pbxproj since it has been ' |
| 723 | 'replaced with flutter_assets.'); |
| 724 | lines.remove(l9); |
| 725 | lines.remove(l10); |
| 726 | lines.remove(l11); |
| 727 | lines.remove(l12); |
| 728 | } |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 729 | |
Alexandre Ardhuin | d927c93 | 2018-09-12 08:29:29 +0200 | [diff] [blame] | 730 | final StringBuffer buffer = StringBuffer(); |
Sarah Zakarias | 5e18c07 | 2017-12-14 17:27:25 +0100 | [diff] [blame] | 731 | lines.forEach(buffer.writeln); |
| 732 | await xcodeProjectFile.writeAsString(buffer.toString()); |
| 733 | return true; |
Chris Bracken | 7fb7852 | 2017-12-15 18:13:10 -0800 | [diff] [blame] | 734 | } |