fix(shorebird_cli): provide exportOptionsPlist to all ipa build commands (#1683)

This commit is contained in:
Bryan Oltman
2024-02-01 13:31:30 -05:00
committed by GitHub
parent 34d0b362f7
commit 8813a6ee41
4 changed files with 100 additions and 74 deletions
@@ -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 = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>manageAppVersionAndBuildNumber</key>
<false/>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadBitcode</key>
<false/>
<key>method</key>
<string>$exportMethod</string>
</dict>
</plist>
''';
final tempDir = Directory.systemTemp.createTempSync();
final exportPlistFile = File(p.join(tempDir.path, 'ExportOptions.plist'))
..createSync(recursive: true)
..writeAsStringSync(plistContents);
return exportPlistFile;
}
}
@@ -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 = <Arch, ArchMetadata>{
@@ -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 = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>manageAppVersionAndBuildNumber</key>
<false/>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadBitcode</key>
<false/>
<key>method</key>
<string>${exportMethod.argName}</string>
</dict>
</plist>
''';
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<void> buildIosFramework() async {
return _runShorebirdBuildCommand(() async {
@@ -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<String>;
final exportOptionsPlistArg = capturedArgs
.whereType<String>()
.firstWhereOrNull((arg) => arg.contains(exportOptionsPlistArgName));
expect(exportOptionsPlistArg, isNotNull);
expect(exitCode, ExitCode.success.code);
});
@@ -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';