Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shorebird_cli): add shorebird build ipa #534

Merged
merged 2 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/shorebird_cli/lib/src/commands/build/build.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'build_apk_command.dart';
export 'build_app_bundle_command.dart';
export 'build_command.dart';
export 'build_ipa_command.dart';
24 changes: 4 additions & 20 deletions packages/shorebird_cli/lib/src/commands/build/build_command.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validation_mixin.dart';

/// {@template build_command}
///
/// `shorebird build`
/// Build a new release of your application.
/// {@endtemplate}
class BuildCommand extends ShorebirdCommand
with ShorebirdValidationMixin, ShorebirdConfigMixin, ShorebirdBuildMixin {
class BuildCommand extends ShorebirdCommand {
/// {@macro build_command}
BuildCommand({required super.logger}) {
addSubcommand(
BuildApkCommand(
auth: auth,
logger: logger,
validators: validators,
),
);
addSubcommand(
BuildAppBundleCommand(
auth: auth,
logger: logger,
validators: validators,
),
);
addSubcommand(BuildApkCommand(logger: logger));
addSubcommand(BuildAppBundleCommand(logger: logger));
addSubcommand(BuildIpaCommand(logger: logger));
}

@override
Expand Down
104 changes: 104 additions & 0 deletions packages/shorebird_cli/lib/src/commands/build/build_ipa_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'dart:io';

import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth_logger_mixin.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validation_mixin.dart';

/// {@template build_ipa_command}
/// `shorebird build ipa`
/// Builds an .xcarchive and optionally .ipa for an iOS app to be generated for
/// App Store submission.
/// {@endtemplate}
class BuildIpaCommand extends ShorebirdCommand
with
AuthLoggerMixin,
ShorebirdValidationMixin,
ShorebirdConfigMixin,
ShorebirdBuildMixin {
/// {@macro build_ipa_command}
BuildIpaCommand({required super.logger, super.auth, super.validators}) {
argParser
..addOption(
'target',
abbr: 't',
help: 'The main entrypoint file of the application.',
)
..addOption(
'flavor',
help: 'The product flavor to use when building the app.',
)
..addFlag(
'codesign',
help:
'''Codesign the application bundle (only available on device builds).''',
);
}

@override
String get description =>
'''Builds an .xcarchive and optionally .ipa for an iOS app to be generated for App Store submission.''';

@override
String get name => 'ipa';

@override
Future<int> run() async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to verify that the current OS can build ipas? I'm sure flutter build ipa will spit up appropriately so maybe we don't really need to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was planning to leverage the error handling built into the flutter tool but happy to adjust this

if (!auth.isAuthenticated) {
printNeedsAuthInstructions();
return ExitCode.noUser.code;
}

final validationIssues = await runValidators();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fun wrinkle – the default validators list only contains AndroidInternetPermissionValidator, which doesn't apply here. I think we might need to have something similar in the app's entitlements file and/or its Info.plist.

if (validationIssuesContainsError(validationIssues)) {
logValidationFailure(issues: validationIssues);
return ExitCode.config.code;
}

final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final codesign = results['codesign'] as bool;

if (!codesign) {
logger.warn('''
Codesigning is disabled. You must manually codesign before deploying to devices.''');
}

final buildProgress = logger.progress('Building ipa');
try {
await buildIpa(
flavor: flavor,
target: target,
codesign: codesign,
);
} on ProcessException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
return ExitCode.software.code;
}

buildProgress.complete();

const xcarchivePath = './build/ios/archive/Runner.xcarchive';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would probably be good to do a p.join here, even though this should work anywhere you can build an ipa

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, will adjust in the next PR 👍


logger.info('''
📦 Generated an xcode archive at:
${lightCyan.wrap(xcarchivePath)}''');

if (!codesign) {
logger.info(
'Codesigning disabled via "--no-codesign". Skipping ipa generation.',
);
return ExitCode.success.code;
}

const ipaPath = './build/ios/ipa/Runner.ipa';

logger.info('''
📦 Generated an ipa at:
${lightCyan.wrap(ipaPath)}''');

return ExitCode.success.code;
}
}
32 changes: 32 additions & 0 deletions packages/shorebird_cli/lib/src/shorebird_build_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,36 @@ mixin ShorebirdBuildMixin on ShorebirdCommand {
);
}
}

Future<void> buildIpa({
String? flavor,
String? target,
bool codesign = true,
}) async {
const executable = 'flutter';
final arguments = [
'build',
'ipa',
'--release',
if (flavor != null) '--flavor=$flavor',
if (target != null) '--target=$target',
if (!codesign) '--no-codesign',
...results.rest,
];

final result = await process.run(
executable,
arguments,
runInShell: true,
);

if (result.exitCode != ExitCode.success.code) {
throw ProcessException(
'flutter',
arguments,
result.stderr.toString(),
result.exitCode,
);
}
}
}
Loading