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'; |
| 6 | import 'dart:convert' show JSON; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 7 | |
| 8 | import 'package:path/path.dart' as path; |
| 9 | |
| 10 | import '../application_package.dart'; |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 11 | import '../base/context.dart'; |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 12 | import '../base/file_system.dart'; |
Todd Volkert | 016b5ab | 2017-01-09 08:37:00 -0800 | [diff] [blame] | 13 | import '../base/io.dart'; |
Todd Volkert | 417c2f2 | 2017-01-25 16:06:41 -0800 | [diff] [blame] | 14 | import '../base/platform.dart'; |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 15 | import '../base/process.dart'; |
Todd Volkert | 60b19b2 | 2016-11-30 08:42:42 -0800 | [diff] [blame] | 16 | import '../base/process_manager.dart'; |
Chinmay Garde | 66fee3a | 2016-05-23 12:58:42 -0700 | [diff] [blame] | 17 | import '../build_info.dart'; |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 18 | import '../flx.dart' as flx; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 19 | import '../globals.dart'; |
| 20 | import '../services.dart'; |
Chris Bracken | a45e4e9 | 2016-09-26 13:25:57 -0700 | [diff] [blame] | 21 | import 'xcodeproj.dart'; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 22 | |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 23 | const int kXcodeRequiredVersionMajor = 7; |
Yegor Jbanov | 23e634a | 2016-03-10 13:08:58 -0800 | [diff] [blame] | 24 | const int kXcodeRequiredVersionMinor = 0; |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 25 | |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 26 | class XCode { |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 27 | XCode() { |
| 28 | _eulaSigned = false; |
| 29 | |
| 30 | try { |
| 31 | _xcodeSelectPath = runSync(<String>['xcode-select', '--print-path']); |
Dan Rubel | 08d7880 | 2016-10-02 12:23:03 -0400 | [diff] [blame] | 32 | if (_xcodeSelectPath == null || _xcodeSelectPath.trim().isEmpty) { |
| 33 | _isInstalled = false; |
| 34 | return; |
| 35 | } |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 36 | _isInstalled = true; |
| 37 | |
| 38 | _xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', '); |
| 39 | |
Devon Carew | 4ac1868 | 2016-03-28 16:20:43 -0700 | [diff] [blame] | 40 | if (!xcodeVersionRegex.hasMatch(_xcodeVersionText)) { |
| 41 | _isInstalled = false; |
| 42 | } else { |
| 43 | try { |
| 44 | printTrace('xcrun clang'); |
Todd Volkert | 9ba6078 | 2017-01-24 10:09:29 -0800 | [diff] [blame] | 45 | ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']); |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 46 | |
Devon Carew | 4ac1868 | 2016-03-28 16:20:43 -0700 | [diff] [blame] | 47 | if (result.stdout != null && result.stdout.contains('license')) |
| 48 | _eulaSigned = false; |
| 49 | else if (result.stderr != null && result.stderr.contains('license')) |
| 50 | _eulaSigned = false; |
| 51 | else |
| 52 | _eulaSigned = true; |
| 53 | } catch (error) { |
| 54 | } |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 55 | } |
| 56 | } catch (error) { |
| 57 | _isInstalled = false; |
| 58 | } |
| 59 | } |
| 60 | |
Devon Carew | 0fb288c | 2016-03-02 21:14:11 -0800 | [diff] [blame] | 61 | /// Returns [XCode] active in the current app context. |
John McCutchan | 618030e | 2016-11-29 11:22:48 -0800 | [diff] [blame] | 62 | static XCode get instance => context[XCode]; |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 63 | |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 64 | bool get isInstalledAndMeetsVersionCheck => isInstalled && xcodeVersionSatisfactory; |
| 65 | |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 66 | String _xcodeSelectPath; |
| 67 | String get xcodeSelectPath => _xcodeSelectPath; |
| 68 | |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 69 | bool _isInstalled; |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 70 | bool get isInstalled => _isInstalled; |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 71 | |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 72 | bool _eulaSigned; |
Devon Carew | 16f9e38 | 2016-02-21 00:41:14 -0800 | [diff] [blame] | 73 | /// Has the EULA been signed? |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 74 | bool get eulaSigned => _eulaSigned; |
Devon Carew | 16f9e38 | 2016-02-21 00:41:14 -0800 | [diff] [blame] | 75 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 76 | String _xcodeVersionText; |
Devon Carew | c3eec6e | 2016-03-24 13:36:32 -0700 | [diff] [blame] | 77 | String get xcodeVersionText => _xcodeVersionText; |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 78 | |
Devon Carew | 4ac1868 | 2016-03-28 16:20:43 -0700 | [diff] [blame] | 79 | final RegExp xcodeVersionRegex = new RegExp(r'Xcode ([0-9.]+)'); |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 80 | |
Devon Carew | 4ac1868 | 2016-03-28 16:20:43 -0700 | [diff] [blame] | 81 | bool get xcodeVersionSatisfactory { |
| 82 | if (!xcodeVersionRegex.hasMatch(xcodeVersionText)) |
| 83 | return false; |
| 84 | |
| 85 | String version = xcodeVersionRegex.firstMatch(xcodeVersionText).group(1); |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 86 | List<String> components = version.split('.'); |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 87 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 88 | int major = int.parse(components[0]); |
| 89 | int minor = components.length == 1 ? 0 : int.parse(components[1]); |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 90 | |
Devon Carew | 25f332d | 2016-03-23 16:59:56 -0700 | [diff] [blame] | 91 | return _xcodeVersionCheckValid(major, minor); |
Chinmay Garde | c5056b9 | 2016-02-19 13:19:11 -0800 | [diff] [blame] | 92 | } |
Devon Carew | 7ae6f7f | 2016-02-16 18:23:43 -0800 | [diff] [blame] | 93 | } |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 94 | |
Chinmay Garde | 7e59a9e | 2016-02-22 11:43:52 -0800 | [diff] [blame] | 95 | bool _xcodeVersionCheckValid(int major, int minor) { |
| 96 | if (major > kXcodeRequiredVersionMajor) |
| 97 | return true; |
| 98 | |
| 99 | if (major == kXcodeRequiredVersionMajor) |
| 100 | return minor >= kXcodeRequiredVersionMinor; |
| 101 | |
| 102 | return false; |
| 103 | } |
| 104 | |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 105 | Future<XcodeBuildResult> buildXcodeProject({ |
Todd Volkert | 904d524 | 2016-10-13 16:17:50 -0700 | [diff] [blame] | 106 | BuildableIOSApp app, |
Adam Barth | 612a097 | 2016-06-02 08:57:13 -0700 | [diff] [blame] | 107 | BuildMode mode, |
| 108 | String target: flx.defaultMainPath, |
| 109 | bool buildForDevice, |
| 110 | bool codesign: true |
| 111 | }) async { |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 112 | String flutterProjectPath = fs.currentDirectory.path; |
Adam Barth | 8d56fae | 2016-07-06 13:13:28 -0700 | [diff] [blame] | 113 | updateXcodeGeneratedProperties(flutterProjectPath, mode, target); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 114 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 115 | if (!_checkXcodeVersion()) |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 116 | return new XcodeBuildResult(false); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 117 | |
| 118 | // Before the build, all service definitions must be updated and the dylibs |
| 119 | // copied over to a location that is suitable for Xcodebuild to find them. |
| 120 | |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 121 | await _addServicesToBundle(fs.directory(app.appDirectory)); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 122 | |
| 123 | List<String> commands = <String>[ |
Chinmay Garde | 66e1006 | 2016-03-25 15:36:45 -0700 | [diff] [blame] | 124 | '/usr/bin/env', |
| 125 | 'xcrun', |
| 126 | 'xcodebuild', |
Chinmay Garde | 0bf68cc | 2016-04-06 12:33:03 -0700 | [diff] [blame] | 127 | 'clean', |
| 128 | 'build', |
Chinmay Garde | 66e1006 | 2016-03-25 15:36:45 -0700 | [diff] [blame] | 129 | '-configuration', 'Release', |
| 130 | 'ONLY_ACTIVE_ARCH=YES', |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 131 | ]; |
| 132 | |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 133 | List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync(); |
Collin Jackson | 6073a7e | 2016-07-12 17:30:32 -0700 | [diff] [blame] | 134 | for (FileSystemEntity entity in contents) { |
| 135 | if (path.extension(entity.path) == '.xcworkspace') { |
| 136 | commands.addAll(<String>[ |
| 137 | '-workspace', path.basename(entity.path), |
| 138 | '-scheme', path.basenameWithoutExtension(entity.path), |
Chris Bracken | a69c11f | 2016-09-02 17:01:11 -0700 | [diff] [blame] | 139 | "BUILD_DIR=${path.absolute(getIosBuildDirectory())}", |
Collin Jackson | 6073a7e | 2016-07-12 17:30:32 -0700 | [diff] [blame] | 140 | ]); |
| 141 | break; |
| 142 | } |
| 143 | } |
| 144 | |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 145 | if (buildForDevice) { |
| 146 | commands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']); |
| 147 | } else { |
| 148 | commands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']); |
| 149 | } |
| 150 | |
Todd Volkert | 74e3167 | 2016-11-10 10:30:17 -0800 | [diff] [blame] | 151 | if (!codesign) { |
| 152 | commands.addAll( |
| 153 | <String>[ |
| 154 | 'CODE_SIGNING_ALLOWED=NO', |
| 155 | 'CODE_SIGNING_REQUIRED=NO', |
| 156 | 'CODE_SIGNING_IDENTITY=""' |
| 157 | ] |
| 158 | ); |
| 159 | } |
| 160 | |
Ian Hickson | 0d21e69 | 2016-06-14 18:16:55 -0700 | [diff] [blame] | 161 | RunResult result = await runAsync( |
| 162 | commands, |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 163 | workingDirectory: app.appDirectory, |
Ian Hickson | 0d21e69 | 2016-06-14 18:16:55 -0700 | [diff] [blame] | 164 | allowReentrantFlutter: true |
| 165 | ); |
Devon Carew | f68d86d | 2016-03-03 15:10:45 -0800 | [diff] [blame] | 166 | |
| 167 | if (result.exitCode != 0) { |
| 168 | if (result.stderr.isNotEmpty) |
| 169 | printStatus(result.stderr); |
| 170 | if (result.stdout.isNotEmpty) |
| 171 | printStatus(result.stdout); |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 172 | return new XcodeBuildResult(false, stdout: result.stdout, stderr: result.stderr); |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 173 | } else { |
| 174 | // Look for 'clean build/Release-iphoneos/Runner.app'. |
| 175 | RegExp regexp = new RegExp(r' clean (\S*\.app)$', multiLine: true); |
| 176 | Match match = regexp.firstMatch(result.stdout); |
| 177 | String outputDir; |
| 178 | if (match != null) |
Devon Carew | 1448358 | 2016-08-09 14:38:13 -0700 | [diff] [blame] | 179 | outputDir = path.join(app.appDirectory, match.group(1)); |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 180 | return new XcodeBuildResult(true, output: outputDir); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | void diagnoseXcodeBuildFailure(XcodeBuildResult result) { |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 185 | File plistFile = fs.file('ios/Runner/Info.plist'); |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 186 | if (plistFile.existsSync()) { |
| 187 | String plistContent = plistFile.readAsStringSync(); |
| 188 | if (plistContent.contains('com.yourcompany')) { |
| 189 | printError(''); |
| 190 | printError('It appears that your application still contains the default signing identifier.'); |
| 191 | printError("Try replacing 'com.yourcompany' with your signing id"); |
| 192 | printError('in ${plistFile.absolute.path}'); |
| 193 | return; |
| 194 | } |
| 195 | } |
| 196 | if (result.stdout?.contains('Code Sign error') == true) { |
| 197 | printError(''); |
| 198 | printError('It appears that there was a problem signing your application prior to installation on the device.'); |
| 199 | printError(''); |
| 200 | if (plistFile.existsSync()) { |
| 201 | printError('Verify that the CFBundleIdentifier in the Info.plist file is your signing id'); |
| 202 | printError(' ${plistFile.absolute.path}'); |
| 203 | printError(''); |
| 204 | } |
| 205 | printError("Try launching XCode and selecting 'Product > Build' to fix the problem:"); |
| 206 | printError(" open ios/Runner.xcodeproj"); |
| 207 | return; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 208 | } |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 209 | } |
Devon Carew | f68d86d | 2016-03-03 15:10:45 -0800 | [diff] [blame] | 210 | |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 211 | class XcodeBuildResult { |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 212 | XcodeBuildResult(this.success, {this.output, this.stdout, this.stderr}); |
Devon Carew | 7c47837 | 2016-05-29 15:07:41 -0700 | [diff] [blame] | 213 | |
| 214 | final bool success; |
| 215 | final String output; |
Dan Rubel | 573eaf0 | 2016-09-16 17:59:43 -0400 | [diff] [blame] | 216 | final String stdout; |
| 217 | final String stderr; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 218 | } |
| 219 | |
| 220 | final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*'); |
| 221 | final String _xcodeRequirement = 'Xcode 7.0 or greater is required to develop for iOS.'; |
| 222 | |
| 223 | bool _checkXcodeVersion() { |
Todd Volkert | 417c2f2 | 2017-01-25 16:06:41 -0800 | [diff] [blame] | 224 | if (!platform.isMacOS) |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 225 | return false; |
| 226 | try { |
| 227 | String version = runCheckedSync(<String>['xcodebuild', '-version']); |
| 228 | Match match = _xcodeVersionRegExp.firstMatch(version); |
| 229 | if (int.parse(match[1]) < 7) { |
| 230 | printError('Found "${match[0]}". $_xcodeRequirement'); |
| 231 | return false; |
| 232 | } |
| 233 | } catch (e) { |
| 234 | printError('Cannot find "xcodebuid". $_xcodeRequirement'); |
| 235 | return false; |
| 236 | } |
| 237 | return true; |
| 238 | } |
| 239 | |
Ian Hickson | d745e20 | 2016-03-12 00:32:34 -0800 | [diff] [blame] | 240 | Future<Null> _addServicesToBundle(Directory bundle) async { |
Devon Carew | c9010c9 | 2016-05-03 09:09:00 -0700 | [diff] [blame] | 241 | List<Map<String, String>> services = <Map<String, String>>[]; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 242 | printTrace("Trying to resolve native pub services."); |
| 243 | |
| 244 | // Step 1: Parse the service configuration yaml files present in the service |
| 245 | // pub packages. |
| 246 | await parseServiceConfigs(services); |
| 247 | printTrace("Found ${services.length} service definition(s)."); |
| 248 | |
| 249 | // Step 2: Copy framework dylibs to the correct spot for xcodebuild to pick up. |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 250 | Directory frameworksDirectory = fs.directory(path.join(bundle.path, "Frameworks")); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 251 | await _copyServiceFrameworks(services, frameworksDirectory); |
| 252 | |
| 253 | // Step 3: Copy the service definitions manifest at the correct spot for |
| 254 | // xcodebuild to pick up. |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 255 | File manifestFile = fs.file(path.join(bundle.path, "ServiceDefinitions.json")); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 256 | _copyServiceDefinitionsManifest(services, manifestFile); |
| 257 | } |
| 258 | |
Ian Hickson | d745e20 | 2016-03-12 00:32:34 -0800 | [diff] [blame] | 259 | Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Directory frameworksDirectory) async { |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 260 | printTrace("Copying service frameworks to '${path.absolute(frameworksDirectory.path)}'."); |
| 261 | frameworksDirectory.createSync(recursive: true); |
| 262 | for (Map<String, String> service in services) { |
| 263 | String dylibPath = await getServiceFromUrl(service['ios-framework'], service['root'], service['name']); |
Todd Volkert | 8bb2703 | 2017-01-06 16:51:44 -0800 | [diff] [blame] | 264 | File dylib = fs.file(dylibPath); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 265 | printTrace("Copying ${dylib.path} into bundle."); |
| 266 | if (!dylib.existsSync()) { |
| 267 | printError("The service dylib '${dylib.path}' does not exist."); |
| 268 | continue; |
| 269 | } |
| 270 | // Shell out so permissions on the dylib are preserved. |
Devon Carew | c9010c9 | 2016-05-03 09:09:00 -0700 | [diff] [blame] | 271 | runCheckedSync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]); |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 272 | } |
| 273 | } |
| 274 | |
| 275 | void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) { |
| 276 | printTrace("Creating service definitions manifest at '${manifest.path}'"); |
Devon Carew | c9010c9 | 2016-05-03 09:09:00 -0700 | [diff] [blame] | 277 | 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] | 278 | 'name': service['name'], |
| 279 | // Since we have already moved it to the Frameworks directory. Strip away |
| 280 | // the directory and basenames. |
| 281 | 'framework': path.basenameWithoutExtension(service['ios-framework']) |
| 282 | }).toList(); |
Devon Carew | c9010c9 | 2016-05-03 09:09:00 -0700 | [diff] [blame] | 283 | Map<String, dynamic> json = <String, dynamic>{ 'services' : jsonServices }; |
Devon Carew | 67046f9 | 2016-02-20 22:00:11 -0800 | [diff] [blame] | 284 | manifest.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true); |
| 285 | } |