diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index 3d739b581d6..effe42737fa 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -76,9 +76,8 @@ class BuildIOSCommand extends BuildSubCommand { status.stop(); if (!result.success) { - printError('Encountered error while building for $logTarget.'); - diagnoseXcodeBuildFailure(result); - throwToolExit(''); + await diagnoseXcodeBuildFailure(result); + throwToolExit('Encountered error while building for $logTarget.'); } if (result.output != null) diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 8408cc54ab9..4fd931ebdc5 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -199,7 +199,7 @@ class IOSDevice extends Device { XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: mode, target: mainPath, buildForDevice: true); if (!buildResult.success) { printError('Could not build the precompiled application for the device.'); - diagnoseXcodeBuildFailure(buildResult); + await diagnoseXcodeBuildFailure(buildResult); printError(''); return new LaunchResult.failed(); } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 95231da20a8..2129afbc559 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert' show JSON; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import '../application_package.dart'; @@ -113,7 +114,7 @@ Future buildXcodeProject({ updateXcodeGeneratedProperties(flutterProjectPath, mode, target); if (!_checkXcodeVersion()) - return new XcodeBuildResult(false); + return new XcodeBuildResult(success: false); // Before the build, all service definitions must be updated and the dylibs // copied over to a location that is suitable for Xcodebuild to find them. @@ -169,7 +170,16 @@ Future buildXcodeProject({ printStatus(result.stderr); if (result.stdout.isNotEmpty) printStatus(result.stdout); - return new XcodeBuildResult(false, stdout: result.stdout, stderr: result.stderr); + return new XcodeBuildResult( + success: false, + stdout: result.stdout, + stderr: result.stderr, + xcodeBuildExecution: new XcodeBuildExecution( + commands, + app.appDirectory, + buildForPhysicalDevice: buildForDevice, + ), + ); } else { // Look for 'clean build/Release-iphoneos/Runner.app'. RegExp regexp = new RegExp(r' clean (\S*\.app)$', multiLine: true); @@ -177,11 +187,11 @@ Future buildXcodeProject({ String outputDir; if (match != null) outputDir = path.join(app.appDirectory, match.group(1)); - return new XcodeBuildResult(true, output: outputDir); + return new XcodeBuildResult(success:true, output: outputDir); } } -void diagnoseXcodeBuildFailure(XcodeBuildResult result) { +Future diagnoseXcodeBuildFailure(XcodeBuildResult result) async { File plistFile = fs.file('ios/Runner/Info.plist'); if (plistFile.existsSync()) { String plistContent = plistFile.readAsStringSync(); @@ -206,15 +216,73 @@ void diagnoseXcodeBuildFailure(XcodeBuildResult result) { printError(" open ios/Runner.xcodeproj"); return; } + if (result.xcodeBuildExecution != null) { + assert(result.xcodeBuildExecution.buildForPhysicalDevice != null); + assert(result.xcodeBuildExecution.buildCommands != null); + assert(result.xcodeBuildExecution.appDirectory != null); + if (result.xcodeBuildExecution.buildForPhysicalDevice && + result.xcodeBuildExecution.buildCommands.contains('build')) { + RunResult checkBuildSettings = await runAsync( + result.xcodeBuildExecution.buildCommands..add('-showBuildSettings'), + workingDirectory: result.xcodeBuildExecution.appDirectory, + allowReentrantFlutter: true + ); + // Make sure the user has specified at least the DEVELOPMENT_TEAM (for automatic Xcode 8) + // signing or the PROVISIONING_PROFILE (for manual signing or Xcode 7). + if (checkBuildSettings.exitCode == 0 && + !checkBuildSettings.stdout?.contains(new RegExp(r'\bDEVELOPMENT_TEAM\b')) == true && + !checkBuildSettings.stdout?.contains(new RegExp(r'\bPROVISIONING_PROFILE\b')) == true) { + printError(''' +═══════════════════════════════════════════════════════════════════════════════════ +Building an iOS app requires a selected Development Team with a Provisioning Profile +Please ensure that a Development Team is selected by: + 1- Opening the Flutter project's Xcode target with + open ios/Runner.xcodeproj + 2- Select the 'Runner' project in the navigator then the 'Runner' target + in the project settings + 3- In the 'General' tab, make sure a 'Development Team' is selected\n +For more information, please visit: + https://flutter.io/setup/#deploy-to-ios-devices\n +Or run on an iOS simulator +═══════════════════════════════════════════════════════════════════════════════════'''); + } + } + } } class XcodeBuildResult { - XcodeBuildResult(this.success, {this.output, this.stdout, this.stderr}); + XcodeBuildResult( + { + @required this.success, + this.output, + this.stdout, + this.stderr, + this.xcodeBuildExecution, + } + ); final bool success; final String output; final String stdout; final String stderr; + /// The invocation of the build that resulted in this result instance. + final XcodeBuildExecution xcodeBuildExecution; +} + +/// Describes an invocation of a Xcode build command. +class XcodeBuildExecution { + XcodeBuildExecution( + this.buildCommands, + this.appDirectory, + { + @required this.buildForPhysicalDevice, + } + ); + + /// The original list of Xcode build commands used to produce this build result. + final List buildCommands; + final String appDirectory; + final bool buildForPhysicalDevice; } final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');