Unverified Commit d401bd78 authored by xster's avatar xster Committed by GitHub

Reduce Xcode noise #3 (#14663)

* Revert "Revert "Reduce xcodebuild noise #2" (#14641)"

This reverts commit 2d47481f.

* Stop scrapping xcodebuild output, get the right build settings

* clone the command params first
parent d45c8fde
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
# found in the LICENSE file. # found in the LICENSE file.
RunCommand() { RunCommand() {
echo "♦ $*" if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@" "$@"
return $? return $?
} }
......
...@@ -210,7 +210,11 @@ class BuildableIOSApp extends IOSApp { ...@@ -210,7 +210,11 @@ class BuildableIOSApp extends IOSApp {
final String appDirectory; final String appDirectory;
/// Build settings of the app's XCode project. /// Build settings of the app's Xcode project.
///
/// These are the build settings as specified in the Xcode project files.
///
/// Build settings may change depending on the parameters passed while building.
final Map<String, String> buildSettings; final Map<String, String> buildSettings;
@override @override
......
...@@ -76,7 +76,7 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -76,7 +76,7 @@ class BuildIOSCommand extends BuildSubCommand {
); );
if (!result.success) { if (!result.success) {
await diagnoseXcodeBuildFailure(result, app); await diagnoseXcodeBuildFailure(result);
throwToolExit('Encountered error while building for $logTarget.'); throwToolExit('Encountered error while building for $logTarget.');
} }
......
...@@ -172,7 +172,7 @@ class IOSDevice extends Device { ...@@ -172,7 +172,7 @@ class IOSDevice extends Device {
); );
if (!buildResult.success) { if (!buildResult.success) {
printError('Could not build the precompiled application for the device.'); printError('Could not build the precompiled application for the device.');
await diagnoseXcodeBuildFailure(buildResult, app); await diagnoseXcodeBuildFailure(buildResult);
printError(''); printError('');
return new LaunchResult.failed(); return new LaunchResult.failed();
} }
......
...@@ -277,26 +277,51 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -277,26 +277,51 @@ Future<XcodeBuildResult> buildXcodeProject({
); );
} }
final List<String> commands = <String>[ final Status cleanStatus =
logger.startProgress('Running Xcode clean...', expectSlowOperation: true);
final RunResult cleanResult = await runAsync(
<String>[
'/usr/bin/env',
'xcrun',
'xcodebuild',
'clean',
'-configuration', configuration,
],
workingDirectory: app.appDirectory,
);
cleanStatus.stop();
if (cleanResult.exitCode != 0) {
throwToolExit('Xcode failed to clean\n${cleanResult.stderr}');
}
final List<String> buildCommands = <String>[
'/usr/bin/env', '/usr/bin/env',
'xcrun', 'xcrun',
'xcodebuild', 'xcodebuild',
'clean',
'build', 'build',
'-configuration', configuration, '-configuration', configuration,
'ONLY_ACTIVE_ARCH=YES', 'ONLY_ACTIVE_ARCH=YES',
]; ];
if (logger.isVerbose) {
// An environment variable to be passed to xcode_backend.sh determining
// whether to echo back executed commands.
buildCommands.add('VERBOSE_SCRIPT_LOGGING=YES');
} else {
// This will print warnings and errors only.
buildCommands.add('-quiet');
}
if (developmentTeam != null) { if (developmentTeam != null) {
commands.add('DEVELOPMENT_TEAM=$developmentTeam'); buildCommands.add('DEVELOPMENT_TEAM=$developmentTeam');
commands.add('-allowProvisioningUpdates'); buildCommands.add('-allowProvisioningUpdates');
commands.add('-allowProvisioningDeviceRegistration'); buildCommands.add('-allowProvisioningDeviceRegistration');
} }
final List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync(); final List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync();
for (FileSystemEntity entity in contents) { for (FileSystemEntity entity in contents) {
if (fs.path.extension(entity.path) == '.xcworkspace') { if (fs.path.extension(entity.path) == '.xcworkspace') {
commands.addAll(<String>[ buildCommands.addAll(<String>[
'-workspace', fs.path.basename(entity.path), '-workspace', fs.path.basename(entity.path),
'-scheme', scheme, '-scheme', scheme,
'BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}', 'BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}',
...@@ -306,13 +331,13 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -306,13 +331,13 @@ Future<XcodeBuildResult> buildXcodeProject({
} }
if (buildForDevice) { if (buildForDevice) {
commands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']); buildCommands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
} else { } else {
commands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']); buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
} }
if (!codesign) { if (!codesign) {
commands.addAll( buildCommands.addAll(
<String>[ <String>[
'CODE_SIGNING_ALLOWED=NO', 'CODE_SIGNING_ALLOWED=NO',
'CODE_SIGNING_REQUIRED=NO', 'CODE_SIGNING_REQUIRED=NO',
...@@ -321,49 +346,61 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -321,49 +346,61 @@ Future<XcodeBuildResult> buildXcodeProject({
); );
} }
final Status status = logger.startProgress('Running Xcode build...', expectSlowOperation: true); final Status buildStatus =
final RunResult result = await runAsync( logger.startProgress('Running Xcode build...', expectSlowOperation: true);
commands, final RunResult buildResult = await runAsync(
buildCommands,
workingDirectory: app.appDirectory, workingDirectory: app.appDirectory,
allowReentrantFlutter: true allowReentrantFlutter: true
); );
status.stop(); buildStatus.stop();
if (result.exitCode != 0) {
// Run -showBuildSettings again but with the exact same parameters as the build.
final Map<String, String> buildSettings = parseXcodeBuildSettings(runCheckedSync(
new List<String>.from(buildCommands)..add('-showBuildSettings'),
workingDirectory: app.appDirectory,
));
if (buildResult.exitCode != 0) {
printStatus('Failed to build iOS app'); printStatus('Failed to build iOS app');
if (result.stderr.isNotEmpty) { if (buildResult.stderr.isNotEmpty) {
printStatus('Error output from Xcode build:\n↳'); printStatus('Error output from Xcode build:\n↳');
printStatus(result.stderr, indent: 4); printStatus(buildResult.stderr, indent: 4);
} }
if (result.stdout.isNotEmpty) { if (buildResult.stdout.isNotEmpty) {
printStatus('Xcode\'s output:\n↳'); printStatus('Xcode\'s output:\n↳');
printStatus(result.stdout, indent: 4); printStatus(buildResult.stdout, indent: 4);
} }
return new XcodeBuildResult( return new XcodeBuildResult(
success: false, success: false,
stdout: result.stdout, stdout: buildResult.stdout,
stderr: result.stderr, stderr: buildResult.stderr,
xcodeBuildExecution: new XcodeBuildExecution( xcodeBuildExecution: new XcodeBuildExecution(
commands, buildCommands: buildCommands,
app.appDirectory, appDirectory: app.appDirectory,
buildForPhysicalDevice: buildForDevice, buildForPhysicalDevice: buildForDevice,
buildSettings: buildSettings,
), ),
); );
} else { } else {
// Look for 'clean build/<configuration>-<sdk>/Runner.app'. final String expectedOutputDirectory = fs.path.join(
final RegExp regexp = new RegExp(r' clean (.*\.app)$', multiLine: true); buildSettings['TARGET_BUILD_DIR'],
final Match match = regexp.firstMatch(result.stdout); buildSettings['WRAPPER_NAME'],
);
String outputDir; String outputDir;
if (match != null) { if (fs.isDirectorySync(expectedOutputDirectory)) {
final String actualOutputDir = match.group(1).replaceAll('\\ ', ' ');
// Copy app folder to a place where other tools can find it without knowing // Copy app folder to a place where other tools can find it without knowing
// the BuildInfo. // the BuildInfo.
outputDir = actualOutputDir.replaceFirst('/$configuration-', '/'); outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/');
if (fs.isDirectorySync(outputDir)) { if (fs.isDirectorySync(outputDir)) {
// Previous output directory might have incompatible artifacts // Previous output directory might have incompatible artifacts
// (for example, kernel binary files produced from previous `--preview-dart-2` run). // (for example, kernel binary files produced from previous `--preview-dart-2` run).
fs.directory(outputDir).deleteSync(recursive: true); fs.directory(outputDir).deleteSync(recursive: true);
} }
copyDirectorySync(fs.directory(actualOutputDir), fs.directory(outputDir)); copyDirectorySync(fs.directory(expectedOutputDirectory), fs.directory(outputDir));
} else {
printError('Build succeeded but the expected app at $expectedOutputDirectory not found');
} }
return new XcodeBuildResult(success: true, output: outputDir); return new XcodeBuildResult(success: true, output: outputDir);
} }
...@@ -378,8 +415,7 @@ String readGeneratedXcconfig(String appPath) { ...@@ -378,8 +415,7 @@ String readGeneratedXcconfig(String appPath) {
return generatedXcconfigFile.readAsStringSync(); return generatedXcconfigFile.readAsStringSync();
} }
Future<Null> diagnoseXcodeBuildFailure( Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result) async {
XcodeBuildResult result, BuildableIOSApp app) async {
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice && result.xcodeBuildExecution.buildForPhysicalDevice &&
result.stdout?.contains('BCEROR') == true && result.stdout?.contains('BCEROR') == true &&
...@@ -393,14 +429,15 @@ Future<Null> diagnoseXcodeBuildFailure( ...@@ -393,14 +429,15 @@ Future<Null> diagnoseXcodeBuildFailure(
// * PROVISIONING_PROFILE (manual signing) // * PROVISIONING_PROFILE (manual signing)
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice && result.xcodeBuildExecution.buildForPhysicalDevice &&
app.buildSettings != null && !<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(app.buildSettings.containsKey)) { result.xcodeBuildExecution.buildSettings.containsKey)
) {
printError(noDevelopmentTeamInstruction, emphasis: true); printError(noDevelopmentTeamInstruction, emphasis: true);
return; return;
} }
if (result.xcodeBuildExecution != null && if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice && result.xcodeBuildExecution.buildForPhysicalDevice &&
app.id.contains('com.example')) { result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER'].contains('com.example')) {
printError(''); printError('');
printError('It appears that your application still contains the default signing identifier.'); printError('It appears that your application still contains the default signing identifier.');
printError("Try replacing 'com.example' with your signing id in Xcode:"); printError("Try replacing 'com.example' with your signing id in Xcode:");
...@@ -441,10 +478,11 @@ class XcodeBuildResult { ...@@ -441,10 +478,11 @@ class XcodeBuildResult {
/// Describes an invocation of a Xcode build command. /// Describes an invocation of a Xcode build command.
class XcodeBuildExecution { class XcodeBuildExecution {
XcodeBuildExecution( XcodeBuildExecution(
this.buildCommands,
this.appDirectory,
{ {
@required this.buildCommands,
@required this.appDirectory,
@required this.buildForPhysicalDevice, @required this.buildForPhysicalDevice,
@required this.buildSettings,
} }
); );
...@@ -452,6 +490,8 @@ class XcodeBuildExecution { ...@@ -452,6 +490,8 @@ class XcodeBuildExecution {
final List<String> buildCommands; final List<String> buildCommands;
final String appDirectory; final String appDirectory;
final bool buildForPhysicalDevice; final bool buildForPhysicalDevice;
/// The build settings corresponding to the [buildCommands] invocation.
final Map<String, String> buildSettings;
} }
final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*'); final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');
......
...@@ -72,8 +72,12 @@ Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) { ...@@ -72,8 +72,12 @@ Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
final String out = runCheckedSync(<String>[ final String out = runCheckedSync(<String>[
'/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings' '/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings'
]); ]);
return parseXcodeBuildSettings(out);
}
Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) {
final Map<String, String> settings = <String, String>{}; final Map<String, String> settings = <String, String>{};
for (String line in out.split('\n').where(_settingExpr.hasMatch)) { for (String line in showBuildSettingsOutput.split('\n').where(_settingExpr.hasMatch)) {
final Match match = _settingExpr.firstMatch(line); final Match match = _settingExpr.firstMatch(line);
settings[match[1]] = match[2]; settings[match[1]] = match[2];
} }
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import 'dart:async'; import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult; import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
...@@ -243,15 +242,12 @@ void main() { ...@@ -243,15 +242,12 @@ void main() {
}); });
group('Diagnose Xcode build failure', () { group('Diagnose Xcode build failure', () {
BuildableIOSApp app; Map<String, String> buildSettings;
setUp(() { setUp(() {
app = new BuildableIOSApp( buildSettings = <String, String>{
projectBundleId: 'test.app', 'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
buildSettings: <String, String>{ };
'For our purposes': 'a non-empty build settings map is valid',
},
);
}); });
testUsingContext('No provisioning profile shows message', () async { testUsingContext('No provisioning profile shows message', () async {
...@@ -313,13 +309,14 @@ Could not build the precompiled application for the device. ...@@ -313,13 +309,14 @@ Could not build the precompiled application for the device.
Error launching application on iPhone.''', Error launching application on iPhone.''',
xcodeBuildExecution: new XcodeBuildExecution( xcodeBuildExecution: new XcodeBuildExecution(
<String>['xcrun', 'xcodebuild', 'blah'], buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
'/blah/blah', appDirectory: '/blah/blah',
buildForPhysicalDevice: true buildForPhysicalDevice: true,
buildSettings: buildSettings,
), ),
); );
await diagnoseXcodeBuildFailure(buildResult, app); await diagnoseXcodeBuildFailure(buildResult);
expect( expect(
testLogger.errorText, testLogger.errorText,
contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'), contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'),
...@@ -393,13 +390,14 @@ Xcode's output: ...@@ -393,13 +390,14 @@ Xcode's output:
Could not build the precompiled application for the device.''', Could not build the precompiled application for the device.''',
xcodeBuildExecution: new XcodeBuildExecution( xcodeBuildExecution: new XcodeBuildExecution(
<String>['xcrun', 'xcodebuild', 'blah'], buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
'/blah/blah', appDirectory: '/blah/blah',
buildForPhysicalDevice: true buildForPhysicalDevice: true,
buildSettings: buildSettings,
), ),
); );
await diagnoseXcodeBuildFailure(buildResult, app); await diagnoseXcodeBuildFailure(buildResult);
expect( expect(
testLogger.errorText, testLogger.errorText,
contains('Building a deployable iOS app requires a selected Development Team with a Provisioning Profile'), contains('Building a deployable iOS app requires a selected Development Team with a Provisioning Profile'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment