blob: 13114c0163c96353566155829f686db8dd086ed2 [file] [log] [blame]
Devon Carew7ae6f7f2016-02-16 18:23:43 -08001// 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 Carew67046f92016-02-20 22:00:11 -08005import 'dart:async';
6import 'dart:convert' show JSON;
Devon Carew67046f92016-02-20 22:00:11 -08007
8import 'package:path/path.dart' as path;
9
10import '../application_package.dart';
Devon Carew7ae6f7f2016-02-16 18:23:43 -080011import '../base/context.dart';
Todd Volkert8bb27032017-01-06 16:51:44 -080012import '../base/file_system.dart';
Todd Volkert016b5ab2017-01-09 08:37:00 -080013import '../base/io.dart';
Todd Volkert417c2f22017-01-25 16:06:41 -080014import '../base/platform.dart';
Devon Carew7ae6f7f2016-02-16 18:23:43 -080015import '../base/process.dart';
Todd Volkert60b19b22016-11-30 08:42:42 -080016import '../base/process_manager.dart';
Chinmay Garde66fee3a2016-05-23 12:58:42 -070017import '../build_info.dart';
Adam Barth612a0972016-06-02 08:57:13 -070018import '../flx.dart' as flx;
Devon Carew67046f92016-02-20 22:00:11 -080019import '../globals.dart';
20import '../services.dart';
Chris Brackena45e4e92016-09-26 13:25:57 -070021import 'xcodeproj.dart';
Devon Carew67046f92016-02-20 22:00:11 -080022
Chinmay Gardec5056b92016-02-19 13:19:11 -080023const int kXcodeRequiredVersionMajor = 7;
Yegor Jbanov23e634a2016-03-10 13:08:58 -080024const int kXcodeRequiredVersionMinor = 0;
Chinmay Gardec5056b92016-02-19 13:19:11 -080025
Devon Carew7ae6f7f2016-02-16 18:23:43 -080026class XCode {
Devon Carewc3eec6e2016-03-24 13:36:32 -070027 XCode() {
28 _eulaSigned = false;
29
30 try {
31 _xcodeSelectPath = runSync(<String>['xcode-select', '--print-path']);
Dan Rubel08d78802016-10-02 12:23:03 -040032 if (_xcodeSelectPath == null || _xcodeSelectPath.trim().isEmpty) {
33 _isInstalled = false;
34 return;
35 }
Devon Carewc3eec6e2016-03-24 13:36:32 -070036 _isInstalled = true;
37
38 _xcodeVersionText = runSync(<String>['xcodebuild', '-version']).replaceAll('\n', ', ');
39
Devon Carew4ac18682016-03-28 16:20:43 -070040 if (!xcodeVersionRegex.hasMatch(_xcodeVersionText)) {
41 _isInstalled = false;
42 } else {
43 try {
44 printTrace('xcrun clang');
Todd Volkert9ba60782017-01-24 10:09:29 -080045 ProcessResult result = processManager.runSync(<String>['/usr/bin/xcrun', 'clang']);
Devon Carewc3eec6e2016-03-24 13:36:32 -070046
Devon Carew4ac18682016-03-28 16:20:43 -070047 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 Carewc3eec6e2016-03-24 13:36:32 -070055 }
56 } catch (error) {
57 _isInstalled = false;
58 }
59 }
60
Devon Carew0fb288c2016-03-02 21:14:11 -080061 /// Returns [XCode] active in the current app context.
John McCutchan618030e2016-11-29 11:22:48 -080062 static XCode get instance => context[XCode];
Devon Carew7ae6f7f2016-02-16 18:23:43 -080063
Chinmay Gardec5056b92016-02-19 13:19:11 -080064 bool get isInstalledAndMeetsVersionCheck => isInstalled && xcodeVersionSatisfactory;
65
Devon Carewc3eec6e2016-03-24 13:36:32 -070066 String _xcodeSelectPath;
67 String get xcodeSelectPath => _xcodeSelectPath;
68
Chinmay Gardec5056b92016-02-19 13:19:11 -080069 bool _isInstalled;
Devon Carewc3eec6e2016-03-24 13:36:32 -070070 bool get isInstalled => _isInstalled;
Chinmay Gardec5056b92016-02-19 13:19:11 -080071
Devon Carewc3eec6e2016-03-24 13:36:32 -070072 bool _eulaSigned;
Devon Carew16f9e382016-02-21 00:41:14 -080073 /// Has the EULA been signed?
Devon Carewc3eec6e2016-03-24 13:36:32 -070074 bool get eulaSigned => _eulaSigned;
Devon Carew16f9e382016-02-21 00:41:14 -080075
Devon Carew25f332d2016-03-23 16:59:56 -070076 String _xcodeVersionText;
Devon Carewc3eec6e2016-03-24 13:36:32 -070077 String get xcodeVersionText => _xcodeVersionText;
Devon Carew25f332d2016-03-23 16:59:56 -070078
Devon Carew4ac18682016-03-28 16:20:43 -070079 final RegExp xcodeVersionRegex = new RegExp(r'Xcode ([0-9.]+)');
Chinmay Gardec5056b92016-02-19 13:19:11 -080080
Devon Carew4ac18682016-03-28 16:20:43 -070081 bool get xcodeVersionSatisfactory {
82 if (!xcodeVersionRegex.hasMatch(xcodeVersionText))
83 return false;
84
85 String version = xcodeVersionRegex.firstMatch(xcodeVersionText).group(1);
Devon Carew25f332d2016-03-23 16:59:56 -070086 List<String> components = version.split('.');
Chinmay Gardec5056b92016-02-19 13:19:11 -080087
Devon Carew25f332d2016-03-23 16:59:56 -070088 int major = int.parse(components[0]);
89 int minor = components.length == 1 ? 0 : int.parse(components[1]);
Chinmay Gardec5056b92016-02-19 13:19:11 -080090
Devon Carew25f332d2016-03-23 16:59:56 -070091 return _xcodeVersionCheckValid(major, minor);
Chinmay Gardec5056b92016-02-19 13:19:11 -080092 }
Devon Carew7ae6f7f2016-02-16 18:23:43 -080093}
Devon Carew67046f92016-02-20 22:00:11 -080094
Chinmay Garde7e59a9e2016-02-22 11:43:52 -080095bool _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 Barth612a0972016-06-02 08:57:13 -0700105Future<XcodeBuildResult> buildXcodeProject({
Todd Volkert904d5242016-10-13 16:17:50 -0700106 BuildableIOSApp app,
Adam Barth612a0972016-06-02 08:57:13 -0700107 BuildMode mode,
108 String target: flx.defaultMainPath,
109 bool buildForDevice,
110 bool codesign: true
111}) async {
Todd Volkert8bb27032017-01-06 16:51:44 -0800112 String flutterProjectPath = fs.currentDirectory.path;
Adam Barth8d56fae2016-07-06 13:13:28 -0700113 updateXcodeGeneratedProperties(flutterProjectPath, mode, target);
Devon Carew67046f92016-02-20 22:00:11 -0800114
Devon Carew67046f92016-02-20 22:00:11 -0800115 if (!_checkXcodeVersion())
Devon Carew7c478372016-05-29 15:07:41 -0700116 return new XcodeBuildResult(false);
Devon Carew67046f92016-02-20 22:00:11 -0800117
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 Volkert8bb27032017-01-06 16:51:44 -0800121 await _addServicesToBundle(fs.directory(app.appDirectory));
Devon Carew67046f92016-02-20 22:00:11 -0800122
123 List<String> commands = <String>[
Chinmay Garde66e10062016-03-25 15:36:45 -0700124 '/usr/bin/env',
125 'xcrun',
126 'xcodebuild',
Chinmay Garde0bf68cc2016-04-06 12:33:03 -0700127 'clean',
128 'build',
Chinmay Garde66e10062016-03-25 15:36:45 -0700129 '-configuration', 'Release',
130 'ONLY_ACTIVE_ARCH=YES',
Devon Carew67046f92016-02-20 22:00:11 -0800131 ];
132
Todd Volkert8bb27032017-01-06 16:51:44 -0800133 List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync();
Collin Jackson6073a7e2016-07-12 17:30:32 -0700134 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 Brackena69c11f2016-09-02 17:01:11 -0700139 "BUILD_DIR=${path.absolute(getIosBuildDirectory())}",
Collin Jackson6073a7e2016-07-12 17:30:32 -0700140 ]);
141 break;
142 }
143 }
144
Devon Carew67046f92016-02-20 22:00:11 -0800145 if (buildForDevice) {
146 commands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
147 } else {
148 commands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
149 }
150
Todd Volkert74e31672016-11-10 10:30:17 -0800151 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 Hickson0d21e692016-06-14 18:16:55 -0700161 RunResult result = await runAsync(
162 commands,
Devon Carew14483582016-08-09 14:38:13 -0700163 workingDirectory: app.appDirectory,
Ian Hickson0d21e692016-06-14 18:16:55 -0700164 allowReentrantFlutter: true
165 );
Devon Carewf68d86d2016-03-03 15:10:45 -0800166
167 if (result.exitCode != 0) {
168 if (result.stderr.isNotEmpty)
169 printStatus(result.stderr);
170 if (result.stdout.isNotEmpty)
171 printStatus(result.stdout);
Dan Rubel573eaf02016-09-16 17:59:43 -0400172 return new XcodeBuildResult(false, stdout: result.stdout, stderr: result.stderr);
Devon Carew7c478372016-05-29 15:07:41 -0700173 } 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 Carew14483582016-08-09 14:38:13 -0700179 outputDir = path.join(app.appDirectory, match.group(1));
Dan Rubel573eaf02016-09-16 17:59:43 -0400180 return new XcodeBuildResult(true, output: outputDir);
181 }
182}
183
184void diagnoseXcodeBuildFailure(XcodeBuildResult result) {
Todd Volkert8bb27032017-01-06 16:51:44 -0800185 File plistFile = fs.file('ios/Runner/Info.plist');
Dan Rubel573eaf02016-09-16 17:59:43 -0400186 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 Carew67046f92016-02-20 22:00:11 -0800208 }
Devon Carew7c478372016-05-29 15:07:41 -0700209}
Devon Carewf68d86d2016-03-03 15:10:45 -0800210
Devon Carew7c478372016-05-29 15:07:41 -0700211class XcodeBuildResult {
Dan Rubel573eaf02016-09-16 17:59:43 -0400212 XcodeBuildResult(this.success, {this.output, this.stdout, this.stderr});
Devon Carew7c478372016-05-29 15:07:41 -0700213
214 final bool success;
215 final String output;
Dan Rubel573eaf02016-09-16 17:59:43 -0400216 final String stdout;
217 final String stderr;
Devon Carew67046f92016-02-20 22:00:11 -0800218}
219
220final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');
221final String _xcodeRequirement = 'Xcode 7.0 or greater is required to develop for iOS.';
222
223bool _checkXcodeVersion() {
Todd Volkert417c2f22017-01-25 16:06:41 -0800224 if (!platform.isMacOS)
Devon Carew67046f92016-02-20 22:00:11 -0800225 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 Hicksond745e202016-03-12 00:32:34 -0800240Future<Null> _addServicesToBundle(Directory bundle) async {
Devon Carewc9010c92016-05-03 09:09:00 -0700241 List<Map<String, String>> services = <Map<String, String>>[];
Devon Carew67046f92016-02-20 22:00:11 -0800242 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 Volkert8bb27032017-01-06 16:51:44 -0800250 Directory frameworksDirectory = fs.directory(path.join(bundle.path, "Frameworks"));
Devon Carew67046f92016-02-20 22:00:11 -0800251 await _copyServiceFrameworks(services, frameworksDirectory);
252
253 // Step 3: Copy the service definitions manifest at the correct spot for
254 // xcodebuild to pick up.
Todd Volkert8bb27032017-01-06 16:51:44 -0800255 File manifestFile = fs.file(path.join(bundle.path, "ServiceDefinitions.json"));
Devon Carew67046f92016-02-20 22:00:11 -0800256 _copyServiceDefinitionsManifest(services, manifestFile);
257}
258
Ian Hicksond745e202016-03-12 00:32:34 -0800259Future<Null> _copyServiceFrameworks(List<Map<String, String>> services, Directory frameworksDirectory) async {
Devon Carew67046f92016-02-20 22:00:11 -0800260 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 Volkert8bb27032017-01-06 16:51:44 -0800264 File dylib = fs.file(dylibPath);
Devon Carew67046f92016-02-20 22:00:11 -0800265 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 Carewc9010c92016-05-03 09:09:00 -0700271 runCheckedSync(<String>['/bin/cp', dylib.path, frameworksDirectory.path]);
Devon Carew67046f92016-02-20 22:00:11 -0800272 }
273}
274
275void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) {
276 printTrace("Creating service definitions manifest at '${manifest.path}'");
Devon Carewc9010c92016-05-03 09:09:00 -0700277 List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => <String, String>{
Devon Carew67046f92016-02-20 22:00:11 -0800278 '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 Carewc9010c92016-05-03 09:09:00 -0700283 Map<String, dynamic> json = <String, dynamic>{ 'services' : jsonServices };
Devon Carew67046f92016-02-20 22:00:11 -0800284 manifest.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
285}