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 @@
# found in the LICENSE file.
RunCommand() {
echo "♦ $*"
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
}
......
......@@ -210,7 +210,11 @@ class BuildableIOSApp extends IOSApp {
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;
@override
......
......@@ -76,7 +76,7 @@ class BuildIOSCommand extends BuildSubCommand {
);
if (!result.success) {
await diagnoseXcodeBuildFailure(result, app);
await diagnoseXcodeBuildFailure(result);
throwToolExit('Encountered error while building for $logTarget.');
}
......
......@@ -172,7 +172,7 @@ class IOSDevice extends Device {
);
if (!buildResult.success) {
printError('Could not build the precompiled application for the device.');
await diagnoseXcodeBuildFailure(buildResult, app);
await diagnoseXcodeBuildFailure(buildResult);
printError('');
return new LaunchResult.failed();
}
......
......@@ -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',
'xcrun',
'xcodebuild',
'clean',
'build',
'-configuration', configuration,
'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) {
commands.add('DEVELOPMENT_TEAM=$developmentTeam');
commands.add('-allowProvisioningUpdates');
commands.add('-allowProvisioningDeviceRegistration');
buildCommands.add('DEVELOPMENT_TEAM=$developmentTeam');
buildCommands.add('-allowProvisioningUpdates');
buildCommands.add('-allowProvisioningDeviceRegistration');
}
final List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync();
for (FileSystemEntity entity in contents) {
if (fs.path.extension(entity.path) == '.xcworkspace') {
commands.addAll(<String>[
buildCommands.addAll(<String>[
'-workspace', fs.path.basename(entity.path),
'-scheme', scheme,
'BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}',
......@@ -306,13 +331,13 @@ Future<XcodeBuildResult> buildXcodeProject({
}
if (buildForDevice) {
commands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
buildCommands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
} else {
commands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
}
if (!codesign) {
commands.addAll(
buildCommands.addAll(
<String>[
'CODE_SIGNING_ALLOWED=NO',
'CODE_SIGNING_REQUIRED=NO',
......@@ -321,49 +346,61 @@ Future<XcodeBuildResult> buildXcodeProject({
);
}
final Status status = logger.startProgress('Running Xcode build...', expectSlowOperation: true);
final RunResult result = await runAsync(
commands,
final Status buildStatus =
logger.startProgress('Running Xcode build...', expectSlowOperation: true);
final RunResult buildResult = await runAsync(
buildCommands,
workingDirectory: app.appDirectory,
allowReentrantFlutter: true
);
status.stop();
if (result.exitCode != 0) {
buildStatus.stop();
// 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');
if (result.stderr.isNotEmpty) {
if (buildResult.stderr.isNotEmpty) {
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(result.stdout, indent: 4);
printStatus(buildResult.stdout, indent: 4);
}
return new XcodeBuildResult(
success: false,
stdout: result.stdout,
stderr: result.stderr,
stdout: buildResult.stdout,
stderr: buildResult.stderr,
xcodeBuildExecution: new XcodeBuildExecution(
commands,
app.appDirectory,
buildCommands: buildCommands,
appDirectory: app.appDirectory,
buildForPhysicalDevice: buildForDevice,
buildSettings: buildSettings,
),
);
} else {
// Look for 'clean build/<configuration>-<sdk>/Runner.app'.
final RegExp regexp = new RegExp(r' clean (.*\.app)$', multiLine: true);
final Match match = regexp.firstMatch(result.stdout);
final String expectedOutputDirectory = fs.path.join(
buildSettings['TARGET_BUILD_DIR'],
buildSettings['WRAPPER_NAME'],
);
String outputDir;
if (match != null) {
final String actualOutputDir = match.group(1).replaceAll('\\ ', ' ');
if (fs.isDirectorySync(expectedOutputDirectory)) {
// Copy app folder to a place where other tools can find it without knowing
// the BuildInfo.
outputDir = actualOutputDir.replaceFirst('/$configuration-', '/');
outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/');
if (fs.isDirectorySync(outputDir)) {
// Previous output directory might have incompatible artifacts
// (for example, kernel binary files produced from previous `--preview-dart-2` run).
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);
}
......@@ -378,8 +415,7 @@ String readGeneratedXcconfig(String appPath) {
return generatedXcconfigFile.readAsStringSync();
}
Future<Null> diagnoseXcodeBuildFailure(
XcodeBuildResult result, BuildableIOSApp app) async {
Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result) async {
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
result.stdout?.contains('BCEROR') == true &&
......@@ -393,14 +429,15 @@ Future<Null> diagnoseXcodeBuildFailure(
// * PROVISIONING_PROFILE (manual signing)
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
app.buildSettings != null &&
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(app.buildSettings.containsKey)) {
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(
result.xcodeBuildExecution.buildSettings.containsKey)
) {
printError(noDevelopmentTeamInstruction, emphasis: true);
return;
}
if (result.xcodeBuildExecution != null &&
result.xcodeBuildExecution.buildForPhysicalDevice &&
app.id.contains('com.example')) {
result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER'].contains('com.example')) {
printError('');
printError('It appears that your application still contains the default signing identifier.');
printError("Try replacing 'com.example' with your signing id in Xcode:");
......@@ -441,10 +478,11 @@ class XcodeBuildResult {
/// Describes an invocation of a Xcode build command.
class XcodeBuildExecution {
XcodeBuildExecution(
this.buildCommands,
this.appDirectory,
{
@required this.buildCommands,
@required this.appDirectory,
@required this.buildForPhysicalDevice,
@required this.buildSettings,
}
);
......@@ -452,6 +490,8 @@ class XcodeBuildExecution {
final List<String> buildCommands;
final String appDirectory;
final bool buildForPhysicalDevice;
/// The build settings corresponding to the [buildCommands] invocation.
final Map<String, String> buildSettings;
}
final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');
......
......@@ -72,8 +72,12 @@ Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
final String out = runCheckedSync(<String>[
'/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings'
]);
return parseXcodeBuildSettings(out);
}
Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) {
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);
settings[match[1]] = match[2];
}
......
......@@ -5,7 +5,6 @@
import 'dart:async';
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/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/ios/mac.dart';
......@@ -243,15 +242,12 @@ void main() {
});
group('Diagnose Xcode build failure', () {
BuildableIOSApp app;
Map<String, String> buildSettings;
setUp(() {
app = new BuildableIOSApp(
projectBundleId: 'test.app',
buildSettings: <String, String>{
'For our purposes': 'a non-empty build settings map is valid',
},
);
buildSettings = <String, String>{
'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
};
});
testUsingContext('No provisioning profile shows message', () async {
......@@ -313,13 +309,14 @@ Could not build the precompiled application for the device.
Error launching application on iPhone.''',
xcodeBuildExecution: new XcodeBuildExecution(
<String>['xcrun', 'xcodebuild', 'blah'],
'/blah/blah',
buildForPhysicalDevice: true
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
appDirectory: '/blah/blah',
buildForPhysicalDevice: true,
buildSettings: buildSettings,
),
);
await diagnoseXcodeBuildFailure(buildResult, app);
await diagnoseXcodeBuildFailure(buildResult);
expect(
testLogger.errorText,
contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'),
......@@ -393,13 +390,14 @@ Xcode's output:
Could not build the precompiled application for the device.''',
xcodeBuildExecution: new XcodeBuildExecution(
<String>['xcrun', 'xcodebuild', 'blah'],
'/blah/blah',
buildForPhysicalDevice: true
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
appDirectory: '/blah/blah',
buildForPhysicalDevice: true,
buildSettings: buildSettings,
),
);
await diagnoseXcodeBuildFailure(buildResult, app);
await diagnoseXcodeBuildFailure(buildResult);
expect(
testLogger.errorText,
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