Skip to content

Commit d401bd7

Browse files
authored
Reduce Xcode noise #3 (flutter#14663)
* Revert "Revert "Reduce xcodebuild noise #2" (flutter#14641)" This reverts commit 2d47481. * Stop scrapping xcodebuild output, get the right build settings * clone the command params first
1 parent d45c8fd commit d401bd7

File tree

7 files changed

+105
-57
lines changed

7 files changed

+105
-57
lines changed

packages/flutter_tools/bin/xcode_backend.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
# found in the LICENSE file.
55

66
RunCommand() {
7-
echo "$*"
7+
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
8+
echo "$*"
9+
fi
810
"$@"
911
return $?
1012
}

packages/flutter_tools/lib/src/application_package.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,11 @@ class BuildableIOSApp extends IOSApp {
210210

211211
final String appDirectory;
212212

213-
/// Build settings of the app's XCode project.
213+
/// Build settings of the app's Xcode project.
214+
///
215+
/// These are the build settings as specified in the Xcode project files.
216+
///
217+
/// Build settings may change depending on the parameters passed while building.
214218
final Map<String, String> buildSettings;
215219

216220
@override

packages/flutter_tools/lib/src/commands/build_ios.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class BuildIOSCommand extends BuildSubCommand {
7676
);
7777

7878
if (!result.success) {
79-
await diagnoseXcodeBuildFailure(result, app);
79+
await diagnoseXcodeBuildFailure(result);
8080
throwToolExit('Encountered error while building for $logTarget.');
8181
}
8282

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class IOSDevice extends Device {
172172
);
173173
if (!buildResult.success) {
174174
printError('Could not build the precompiled application for the device.');
175-
await diagnoseXcodeBuildFailure(buildResult, app);
175+
await diagnoseXcodeBuildFailure(buildResult);
176176
printError('');
177177
return new LaunchResult.failed();
178178
}

packages/flutter_tools/lib/src/ios/mac.dart

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -277,26 +277,51 @@ Future<XcodeBuildResult> buildXcodeProject({
277277
);
278278
}
279279

280-
final List<String> commands = <String>[
280+
final Status cleanStatus =
281+
logger.startProgress('Running Xcode clean...', expectSlowOperation: true);
282+
final RunResult cleanResult = await runAsync(
283+
<String>[
284+
'/usr/bin/env',
285+
'xcrun',
286+
'xcodebuild',
287+
'clean',
288+
'-configuration', configuration,
289+
],
290+
workingDirectory: app.appDirectory,
291+
);
292+
cleanStatus.stop();
293+
if (cleanResult.exitCode != 0) {
294+
throwToolExit('Xcode failed to clean\n${cleanResult.stderr}');
295+
}
296+
297+
final List<String> buildCommands = <String>[
281298
'/usr/bin/env',
282299
'xcrun',
283300
'xcodebuild',
284-
'clean',
285301
'build',
286302
'-configuration', configuration,
287303
'ONLY_ACTIVE_ARCH=YES',
288304
];
289305

306+
if (logger.isVerbose) {
307+
// An environment variable to be passed to xcode_backend.sh determining
308+
// whether to echo back executed commands.
309+
buildCommands.add('VERBOSE_SCRIPT_LOGGING=YES');
310+
} else {
311+
// This will print warnings and errors only.
312+
buildCommands.add('-quiet');
313+
}
314+
290315
if (developmentTeam != null) {
291-
commands.add('DEVELOPMENT_TEAM=$developmentTeam');
292-
commands.add('-allowProvisioningUpdates');
293-
commands.add('-allowProvisioningDeviceRegistration');
316+
buildCommands.add('DEVELOPMENT_TEAM=$developmentTeam');
317+
buildCommands.add('-allowProvisioningUpdates');
318+
buildCommands.add('-allowProvisioningDeviceRegistration');
294319
}
295320

296321
final List<FileSystemEntity> contents = fs.directory(app.appDirectory).listSync();
297322
for (FileSystemEntity entity in contents) {
298323
if (fs.path.extension(entity.path) == '.xcworkspace') {
299-
commands.addAll(<String>[
324+
buildCommands.addAll(<String>[
300325
'-workspace', fs.path.basename(entity.path),
301326
'-scheme', scheme,
302327
'BUILD_DIR=${fs.path.absolute(getIosBuildDirectory())}',
@@ -306,13 +331,13 @@ Future<XcodeBuildResult> buildXcodeProject({
306331
}
307332

308333
if (buildForDevice) {
309-
commands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
334+
buildCommands.addAll(<String>['-sdk', 'iphoneos', '-arch', 'arm64']);
310335
} else {
311-
commands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
336+
buildCommands.addAll(<String>['-sdk', 'iphonesimulator', '-arch', 'x86_64']);
312337
}
313338

314339
if (!codesign) {
315-
commands.addAll(
340+
buildCommands.addAll(
316341
<String>[
317342
'CODE_SIGNING_ALLOWED=NO',
318343
'CODE_SIGNING_REQUIRED=NO',
@@ -321,49 +346,61 @@ Future<XcodeBuildResult> buildXcodeProject({
321346
);
322347
}
323348

324-
final Status status = logger.startProgress('Running Xcode build...', expectSlowOperation: true);
325-
final RunResult result = await runAsync(
326-
commands,
349+
final Status buildStatus =
350+
logger.startProgress('Running Xcode build...', expectSlowOperation: true);
351+
final RunResult buildResult = await runAsync(
352+
buildCommands,
327353
workingDirectory: app.appDirectory,
328354
allowReentrantFlutter: true
329355
);
330-
status.stop();
331-
if (result.exitCode != 0) {
356+
buildStatus.stop();
357+
358+
// Run -showBuildSettings again but with the exact same parameters as the build.
359+
final Map<String, String> buildSettings = parseXcodeBuildSettings(runCheckedSync(
360+
new List<String>.from(buildCommands)..add('-showBuildSettings'),
361+
workingDirectory: app.appDirectory,
362+
));
363+
364+
if (buildResult.exitCode != 0) {
332365
printStatus('Failed to build iOS app');
333-
if (result.stderr.isNotEmpty) {
366+
if (buildResult.stderr.isNotEmpty) {
334367
printStatus('Error output from Xcode build:\n↳');
335-
printStatus(result.stderr, indent: 4);
368+
printStatus(buildResult.stderr, indent: 4);
336369
}
337-
if (result.stdout.isNotEmpty) {
370+
if (buildResult.stdout.isNotEmpty) {
338371
printStatus('Xcode\'s output:\n↳');
339-
printStatus(result.stdout, indent: 4);
372+
printStatus(buildResult.stdout, indent: 4);
340373
}
341374
return new XcodeBuildResult(
342375
success: false,
343-
stdout: result.stdout,
344-
stderr: result.stderr,
376+
stdout: buildResult.stdout,
377+
stderr: buildResult.stderr,
345378
xcodeBuildExecution: new XcodeBuildExecution(
346-
commands,
347-
app.appDirectory,
379+
buildCommands: buildCommands,
380+
appDirectory: app.appDirectory,
348381
buildForPhysicalDevice: buildForDevice,
382+
buildSettings: buildSettings,
349383
),
350384
);
351385
} else {
352-
// Look for 'clean build/<configuration>-<sdk>/Runner.app'.
353-
final RegExp regexp = new RegExp(r' clean (.*\.app)$', multiLine: true);
354-
final Match match = regexp.firstMatch(result.stdout);
386+
final String expectedOutputDirectory = fs.path.join(
387+
buildSettings['TARGET_BUILD_DIR'],
388+
buildSettings['WRAPPER_NAME'],
389+
);
390+
355391
String outputDir;
356-
if (match != null) {
357-
final String actualOutputDir = match.group(1).replaceAll('\\ ', ' ');
392+
if (fs.isDirectorySync(expectedOutputDirectory)) {
358393
// Copy app folder to a place where other tools can find it without knowing
359394
// the BuildInfo.
360-
outputDir = actualOutputDir.replaceFirst('/$configuration-', '/');
395+
outputDir = expectedOutputDirectory.replaceFirst('/$configuration-', '/');
361396
if (fs.isDirectorySync(outputDir)) {
362397
// Previous output directory might have incompatible artifacts
363398
// (for example, kernel binary files produced from previous `--preview-dart-2` run).
364399
fs.directory(outputDir).deleteSync(recursive: true);
365400
}
366-
copyDirectorySync(fs.directory(actualOutputDir), fs.directory(outputDir));
401+
copyDirectorySync(fs.directory(expectedOutputDirectory), fs.directory(outputDir));
402+
} else {
403+
printError('Build succeeded but the expected app at $expectedOutputDirectory not found');
367404
}
368405
return new XcodeBuildResult(success: true, output: outputDir);
369406
}
@@ -378,8 +415,7 @@ String readGeneratedXcconfig(String appPath) {
378415
return generatedXcconfigFile.readAsStringSync();
379416
}
380417

381-
Future<Null> diagnoseXcodeBuildFailure(
382-
XcodeBuildResult result, BuildableIOSApp app) async {
418+
Future<Null> diagnoseXcodeBuildFailure(XcodeBuildResult result) async {
383419
if (result.xcodeBuildExecution != null &&
384420
result.xcodeBuildExecution.buildForPhysicalDevice &&
385421
result.stdout?.contains('BCEROR') == true &&
@@ -393,14 +429,15 @@ Future<Null> diagnoseXcodeBuildFailure(
393429
// * PROVISIONING_PROFILE (manual signing)
394430
if (result.xcodeBuildExecution != null &&
395431
result.xcodeBuildExecution.buildForPhysicalDevice &&
396-
app.buildSettings != null &&
397-
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(app.buildSettings.containsKey)) {
432+
!<String>['DEVELOPMENT_TEAM', 'PROVISIONING_PROFILE'].any(
433+
result.xcodeBuildExecution.buildSettings.containsKey)
434+
) {
398435
printError(noDevelopmentTeamInstruction, emphasis: true);
399436
return;
400437
}
401438
if (result.xcodeBuildExecution != null &&
402439
result.xcodeBuildExecution.buildForPhysicalDevice &&
403-
app.id.contains('com.example')) {
440+
result.xcodeBuildExecution.buildSettings['PRODUCT_BUNDLE_IDENTIFIER'].contains('com.example')) {
404441
printError('');
405442
printError('It appears that your application still contains the default signing identifier.');
406443
printError("Try replacing 'com.example' with your signing id in Xcode:");
@@ -441,17 +478,20 @@ class XcodeBuildResult {
441478
/// Describes an invocation of a Xcode build command.
442479
class XcodeBuildExecution {
443480
XcodeBuildExecution(
444-
this.buildCommands,
445-
this.appDirectory,
446481
{
482+
@required this.buildCommands,
483+
@required this.appDirectory,
447484
@required this.buildForPhysicalDevice,
485+
@required this.buildSettings,
448486
}
449487
);
450488

451489
/// The original list of Xcode build commands used to produce this build result.
452490
final List<String> buildCommands;
453491
final String appDirectory;
454492
final bool buildForPhysicalDevice;
493+
/// The build settings corresponding to the [buildCommands] invocation.
494+
final Map<String, String> buildSettings;
455495
}
456496

457497
final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');

packages/flutter_tools/lib/src/ios/xcodeproj.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,12 @@ Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
7272
final String out = runCheckedSync(<String>[
7373
'/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings'
7474
]);
75+
return parseXcodeBuildSettings(out);
76+
}
77+
78+
Map<String, String> parseXcodeBuildSettings(String showBuildSettingsOutput) {
7579
final Map<String, String> settings = <String, String>{};
76-
for (String line in out.split('\n').where(_settingExpr.hasMatch)) {
80+
for (String line in showBuildSettingsOutput.split('\n').where(_settingExpr.hasMatch)) {
7781
final Match match = _settingExpr.firstMatch(line);
7882
settings[match[1]] = match[2];
7983
}

packages/flutter_tools/test/ios/mac_test.dart

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import 'dart:async';
66

77
import 'package:file/file.dart';
8-
import 'package:flutter_tools/src/application_package.dart';
98
import 'package:flutter_tools/src/base/file_system.dart';
109
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
1110
import 'package:flutter_tools/src/ios/mac.dart';
@@ -243,15 +242,12 @@ void main() {
243242
});
244243

245244
group('Diagnose Xcode build failure', () {
246-
BuildableIOSApp app;
245+
Map<String, String> buildSettings;
247246

248247
setUp(() {
249-
app = new BuildableIOSApp(
250-
projectBundleId: 'test.app',
251-
buildSettings: <String, String>{
252-
'For our purposes': 'a non-empty build settings map is valid',
253-
},
254-
);
248+
buildSettings = <String, String>{
249+
'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
250+
};
255251
});
256252

257253
testUsingContext('No provisioning profile shows message', () async {
@@ -313,13 +309,14 @@ Could not build the precompiled application for the device.
313309
314310
Error launching application on iPhone.''',
315311
xcodeBuildExecution: new XcodeBuildExecution(
316-
<String>['xcrun', 'xcodebuild', 'blah'],
317-
'/blah/blah',
318-
buildForPhysicalDevice: true
312+
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
313+
appDirectory: '/blah/blah',
314+
buildForPhysicalDevice: true,
315+
buildSettings: buildSettings,
319316
),
320317
);
321318

322-
await diagnoseXcodeBuildFailure(buildResult, app);
319+
await diagnoseXcodeBuildFailure(buildResult);
323320
expect(
324321
testLogger.errorText,
325322
contains('No Provisioning Profile was found for your project\'s Bundle Identifier or your device.'),
@@ -393,13 +390,14 @@ Xcode's output:
393390
394391
Could not build the precompiled application for the device.''',
395392
xcodeBuildExecution: new XcodeBuildExecution(
396-
<String>['xcrun', 'xcodebuild', 'blah'],
397-
'/blah/blah',
398-
buildForPhysicalDevice: true
393+
buildCommands: <String>['xcrun', 'xcodebuild', 'blah'],
394+
appDirectory: '/blah/blah',
395+
buildForPhysicalDevice: true,
396+
buildSettings: buildSettings,
399397
),
400398
);
401399

402-
await diagnoseXcodeBuildFailure(buildResult, app);
400+
await diagnoseXcodeBuildFailure(buildResult);
403401
expect(
404402
testLogger.errorText,
405403
contains('Building a deployable iOS app requires a selected Development Team with a Provisioning Profile'),

0 commit comments

Comments
 (0)