From 8813a6ee4162ddc51eb9f07087592cd3530d0f81 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Thu, 1 Feb 2024 13:31:30 -0500 Subject: [PATCH] fix(shorebird_cli): provide exportOptionsPlist to all ipa build commands (#1683) --- .../commands/release/release_ios_command.dart | 88 ++++--------------- .../lib/src/shorebird_build_mixin.dart | 69 ++++++++++++++- .../patch/patch_ios_command_test.dart | 16 ++++ .../release/release_ios_command_test.dart | 1 + 4 files changed, 100 insertions(+), 74 deletions(-) diff --git a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart index 191769f3..50b5b5bb 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart @@ -20,36 +20,6 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; const exportMethodArgName = 'export-method'; const exportOptionsPlistArgName = 'export-options-plist'; -/// {@template export_method} -/// The method used to export the IPA. -/// {@endtemplate} -enum ExportMethod { - appStore('app-store', 'Upload to the App Store'), - adHoc( - 'ad-hoc', - ''' -Test on designated devices that do not need to be registered with the Apple developer account. - Requires a distribution certificate.''', - ), - development( - 'development', - '''Test only on development devices registered with the Apple developer account.''', - ), - enterprise( - 'enterprise', - 'Distribute an app registered with the Apple Developer Enterprise Program.', - ); - - /// {@macro export_method} - const ExportMethod(this.argName, this.description); - - /// The command-line argument name for this export method. - final String argName; - - /// A description of this method and how/when it should be used. - final String description; -} - /// {@template release_ios_command} /// `shorebird release ios` /// Create new app releases for iOS. @@ -140,16 +110,22 @@ make smaller updates to your app. ); return ExitCode.usage.code; } - final exportOptionsPlist = exportPlistArg != null - ? File(exportPlistArg) - : _createExportOptionsPlist( - exportMethod: results[exportMethodArgName] as String, - ); - try { - _validateExportOptionsPlist(exportOptionsPlist); - } catch (error) { - logger.err('$error'); - return ExitCode.usage.code; + + final File? exportOptionsPlist; + if (exportPlistArg != null) { + exportOptionsPlist = File(exportPlistArg); + try { + _validateExportOptionsPlist(exportOptionsPlist); + } catch (error) { + logger.err('$error'); + return ExitCode.usage.code; + } + } else if (results.wasParsed(exportMethodArgName)) { + final exportMethod = + ExportMethod.values.byName(results[exportMethodArgName] as String); + exportOptionsPlist = createExportOptionsPlist(exportMethod: exportMethod); + } else { + exportOptionsPlist = null; } const releasePlatform = ReleasePlatform.ios; @@ -339,36 +315,4 @@ ${styleBold.wrap('Make sure to uncheck "Manage Version and Build Number", or els ); } } - - /// Creates an ExportOptions.plist file, which is used to tell xcodebuild to - /// not manage the app version and build number. If we don't do this, then - /// xcodebuild will increment the build number if it detects an App Store - /// Connect build with the same version and build number. This is a problem - /// for us when patching, as patches need to have the same version and build - /// number as the release they are patching. - /// See - /// https://developer.apple.com/forums/thread/690647?answerId=689925022#689925022 - File _createExportOptionsPlist({required String exportMethod}) { - final plistContents = ''' - - - - - manageAppVersionAndBuildNumber - - signingStyle - automatic - uploadBitcode - - method - $exportMethod - - -'''; - final tempDir = Directory.systemTemp.createTempSync(); - final exportPlistFile = File(p.join(tempDir.path, 'ExportOptions.plist')) - ..createSync(recursive: true) - ..writeAsStringSync(plistContents); - return exportPlistFile; - } } diff --git a/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart index 35e2c761..c747b729 100644 --- a/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; +import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/engine_config.dart'; import 'package:shorebird_cli/src/logger.dart'; @@ -42,6 +43,36 @@ class BuildException implements Exception { final String message; } +/// {@template export_method} +/// The method used to export the IPA. +/// {@endtemplate} +enum ExportMethod { + appStore('app-store', 'Upload to the App Store'), + adHoc( + 'ad-hoc', + ''' +Test on designated devices that do not need to be registered with the Apple developer account. + Requires a distribution certificate.''', + ), + development( + 'development', + '''Test only on development devices registered with the Apple developer account.''', + ), + enterprise( + 'enterprise', + 'Distribute an app registered with the Apple Developer Enterprise Program.', + ); + + /// {@macro export_method} + const ExportMethod(this.argName, this.description); + + /// The command-line argument name for this export method. + final String argName; + + /// A description of this method and how/when it should be used. + final String description; +} + mixin ShorebirdBuildMixin on ShorebirdCommand { // This exists only so tests can get the full list. static const allAndroidArchitectures = { @@ -199,8 +230,8 @@ mixin ShorebirdBuildMixin on ShorebirdCommand { if (flavor != null) '--flavor=$flavor', if (target != null) '--target=$target', if (!codesign) '--no-codesign', - if (codesign && exportOptionsPlist != null) - '--export-options-plist=${exportOptionsPlist.path}', + if (codesign) + '''--export-options-plist=${(exportOptionsPlist ?? createExportOptionsPlist()).path}''', ...results.rest, ]; @@ -231,6 +262,40 @@ mixin ShorebirdBuildMixin on ShorebirdCommand { }); } + /// Creates an ExportOptions.plist file, which is used to tell xcodebuild to + /// not manage the app version and build number. If we don't do this, then + /// xcodebuild will increment the build number if it detects an App Store + /// Connect build with the same version and build number. This is a problem + /// for us when patching, as patches need to have the same version and build + /// number as the release they are patching. + /// See + /// https://developer.apple.com/forums/thread/690647?answerId=689925022#689925022 + File createExportOptionsPlist({ + ExportMethod exportMethod = ExportMethod.appStore, + }) { + final plistContents = ''' + + + + + manageAppVersionAndBuildNumber + + signingStyle + automatic + uploadBitcode + + method + ${exportMethod.argName} + + +'''; + final tempDir = Directory.systemTemp.createTempSync(); + final exportPlistFile = File(p.join(tempDir.path, 'ExportOptions.plist')) + ..createSync(recursive: true) + ..writeAsStringSync(plistContents); + return exportPlistFile; + } + /// Builds a release iOS framework (.xcframework) for the current project. Future buildIosFramework() async { return _runShorebirdBuildCommand(() async { diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart index bb5408fc..9e78d82a 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart @@ -1225,6 +1225,22 @@ Please re-run the release command for this version or create a new release.'''), patchArtifactBundles: any(named: 'patchArtifactBundles'), ), ).called(1); + + // Verify that an export options plist was provided to the build ipa + // command. + const exportOptionsPlistArgName = 'export-options-plist'; + final capturedArgs = verify( + () => shorebirdProcess.run( + 'flutter', + captureAny(), + runInShell: any(named: 'runInShell'), + ), + ).captured.first as List; + final exportOptionsPlistArg = capturedArgs + .whereType() + .firstWhereOrNull((arg) => arg.contains(exportOptionsPlistArgName)); + expect(exportOptionsPlistArg, isNotNull); + expect(exitCode, ExitCode.success.code); }); diff --git a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart index c3521251..59b521be 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart @@ -17,6 +17,7 @@ import 'package:shorebird_cli/src/doctor.dart'; import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/os/operating_system_interface.dart'; import 'package:shorebird_cli/src/platform.dart'; +import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; import 'package:shorebird_cli/src/shorebird_flutter.dart'; import 'package:shorebird_cli/src/shorebird_process.dart';