@@ -96,6 +96,7 @@ Future<XcodeBuildResult> buildXcodeProject({
96
96
bool codesign = true ,
97
97
String deviceID,
98
98
bool configOnly = false ,
99
+ XcodeBuildAction buildAction = XcodeBuildAction .build,
99
100
}) async {
100
101
if (! upgradePbxProjWithFlutterAssets (app.project, globals.logger)) {
101
102
return XcodeBuildResult (success: false );
@@ -321,6 +322,14 @@ Future<XcodeBuildResult> buildXcodeProject({
321
322
buildCommands.add ('COMPILER_INDEX_STORE_ENABLE=NO' );
322
323
buildCommands.addAll (environmentVariablesAsXcodeBuildSettings (globals.platform));
323
324
325
+ if (buildAction == XcodeBuildAction .archive) {
326
+ buildCommands.addAll (< String > [
327
+ '-archivePath' ,
328
+ globals.fs.path.absolute (app.archiveBundlePath),
329
+ 'archive' ,
330
+ ]);
331
+ }
332
+
324
333
final Stopwatch sw = Stopwatch ()..start ();
325
334
initialBuildStatus = globals.logger.startProgress ('Running Xcode build...' , timeout: timeoutConfiguration.slowOperation);
326
335
@@ -333,13 +342,13 @@ Future<XcodeBuildResult> buildXcodeProject({
333
342
initialBuildStatus? .cancel ();
334
343
initialBuildStatus = null ;
335
344
globals.printStatus (
336
- 'Xcode build done.' .padRight (kDefaultStatusPadding + 1 )
345
+ 'Xcode ${ buildAction . name } done.' .padRight (kDefaultStatusPadding + 1 )
337
346
+ getElapsedAsSeconds (sw.elapsed).padLeft (5 ),
338
347
);
339
- globals.flutterUsage.sendTiming ('build' , 'xcode-ios' , Duration (milliseconds: sw.elapsedMilliseconds));
348
+ globals.flutterUsage.sendTiming (buildAction.name , 'xcode-ios' , Duration (milliseconds: sw.elapsedMilliseconds));
340
349
341
350
// Run -showBuildSettings again but with the exact same parameters as the
342
- // build. showBuildSettings is reported to ocassionally timeout. Here, we give
351
+ // build. showBuildSettings is reported to occasionally timeout. Here, we give
343
352
// it a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
344
353
// When there is a timeout, we retry once. See issue #35988.
345
354
final List <String > showBuildSettingsCommand = (List <String >
@@ -398,36 +407,42 @@ Future<XcodeBuildResult> buildXcodeProject({
398
407
),
399
408
);
400
409
} else {
401
- // If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
402
- // For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
403
- // actual directory will end with 'iphonesimulator' for simulator builds.
404
- // The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
405
- String targetBuildDir = buildSettings['TARGET_BUILD_DIR' ];
406
- if (hasWatchCompanion && ! buildForDevice) {
407
- globals.printTrace ('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.' );
408
- targetBuildDir = targetBuildDir.replaceFirst ('iphoneos' , 'iphonesimulator' );
409
- }
410
- final String expectedOutputDirectory = globals.fs.path.join (
411
- targetBuildDir,
412
- buildSettings['WRAPPER_NAME' ],
413
- );
414
-
415
410
String outputDir;
416
- if (globals.fs.isDirectorySync (expectedOutputDirectory)) {
417
- // Copy app folder to a place where other tools can find it without knowing
418
- // the BuildInfo.
419
- outputDir = expectedOutputDirectory.replaceFirst ('/$configuration -' , '/' );
420
- if (globals.fs.isDirectorySync (outputDir)) {
421
- // Previous output directory might have incompatible artifacts
422
- // (for example, kernel binary files produced from previous run).
423
- globals.fs.directory (outputDir).deleteSync (recursive: true );
411
+ if (buildAction == XcodeBuildAction .build) {
412
+ // If the app contains a watch companion target, the sdk argument of xcodebuild has to be omitted.
413
+ // For some reason this leads to TARGET_BUILD_DIR always ending in 'iphoneos' even though the
414
+ // actual directory will end with 'iphonesimulator' for simulator builds.
415
+ // The value of TARGET_BUILD_DIR is adjusted to accommodate for this effect.
416
+ String targetBuildDir = buildSettings['TARGET_BUILD_DIR' ];
417
+ if (hasWatchCompanion && ! buildForDevice) {
418
+ globals.printTrace ('Replacing iphoneos with iphonesimulator in TARGET_BUILD_DIR.' );
419
+ targetBuildDir = targetBuildDir.replaceFirst ('iphoneos' , 'iphonesimulator' );
424
420
}
425
- globals.fsUtils. copyDirectorySync (
426
- globals.fs. directory (expectedOutputDirectory) ,
427
- globals.fs. directory (outputDir) ,
421
+ final String expectedOutputDirectory = globals.fs.path. join (
422
+ targetBuildDir ,
423
+ buildSettings[ 'WRAPPER_NAME' ] ,
428
424
);
425
+ if (globals.fs.isDirectorySync (expectedOutputDirectory)) {
426
+ // Copy app folder to a place where other tools can find it without knowing
427
+ // the BuildInfo.
428
+ outputDir = expectedOutputDirectory.replaceFirst ('/$configuration -' , '/' );
429
+ if (globals.fs.isDirectorySync (outputDir)) {
430
+ // Previous output directory might have incompatible artifacts
431
+ // (for example, kernel binary files produced from previous run).
432
+ globals.fs.directory (outputDir).deleteSync (recursive: true );
433
+ }
434
+ globals.fsUtils.copyDirectorySync (
435
+ globals.fs.directory (expectedOutputDirectory),
436
+ globals.fs.directory (outputDir),
437
+ );
438
+ } else {
439
+ globals.printError ('Build succeeded but the expected app at $expectedOutputDirectory not found' );
440
+ }
429
441
} else {
430
- globals.printError ('Build succeeded but the expected app at $expectedOutputDirectory not found' );
442
+ outputDir = '${globals .fs .path .absolute (app .archiveBundlePath )}.xcarchive' ;
443
+ if (! globals.fs.isDirectorySync (outputDir)) {
444
+ globals.printError ('Archive succeeded but the expected xcarchive at $outputDir not found' );
445
+ }
431
446
}
432
447
return XcodeBuildResult (
433
448
success: true ,
@@ -568,6 +583,24 @@ Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result, Usage flutterUsa
568
583
}
569
584
}
570
585
586
+ /// xcodebuild <buildaction> parameter (see man xcodebuild for details).
587
+ ///
588
+ /// `clean` , `test` , `analyze` , and `install` are not supported.
589
+ enum XcodeBuildAction { build, archive }
590
+
591
+ extension XcodeBuildActionExtension on XcodeBuildAction {
592
+ String get name {
593
+ switch (this ) {
594
+ case XcodeBuildAction .build:
595
+ return 'build' ;
596
+ case XcodeBuildAction .archive:
597
+ return 'archive' ;
598
+ default :
599
+ throw UnsupportedError ('Unknown Xcode build action' );
600
+ }
601
+ }
602
+ }
603
+
571
604
class XcodeBuildResult {
572
605
XcodeBuildResult ({
573
606
@required this .success,
0 commit comments