fix(shorebird_cli): provide exportOptionsPlist to all ipa build commands (#1683)
This commit is contained in:
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user