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