Unverified Commit 802d872d authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Revert "Export an IPA for distribution via "flutter build ipa" without...

Revert "Export an IPA for distribution via "flutter build ipa" without --export-options-plist" (#97616)
parent dff9126d
...@@ -19,8 +19,6 @@ Future<void> main() async { ...@@ -19,8 +19,6 @@ Future<void> main() async {
await inDirectory(flutterProject.rootPath, () async { await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[ await flutter('build', options: <String>[
'xcarchive', 'xcarchive',
'--export-method',
'development',
]); ]);
}); });
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:meta/meta.dart';
import '../base/analyze_size.dart'; import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
...@@ -67,23 +66,12 @@ class BuildIOSCommand extends _BuildIOSSubCommand { ...@@ -67,23 +66,12 @@ class BuildIOSCommand extends _BuildIOSSubCommand {
class BuildIOSArchiveCommand extends _BuildIOSSubCommand { class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
BuildIOSArchiveCommand({required bool verboseHelp}) BuildIOSArchiveCommand({required bool verboseHelp})
: super(verboseHelp: verboseHelp) { : super(verboseHelp: verboseHelp) {
argParser.addOption(
'export-method',
defaultsTo: 'app-store',
allowed: <String>['app-store', 'ad-hoc', 'development'],
help: 'Specify how the IPA will be distributed.',
allowedHelp: <String, String>{
'app-store': 'Upload to the App Store.',
'ad-hoc': 'Distribute to designated devices that do not need to be registered with the Apple developer account. '
'Requires a distribution certificate.',
'development': 'Distribute only to development devices registered with the Apple developer account.',
},
);
argParser.addOption( argParser.addOption(
'export-options-plist', 'export-options-plist',
valueHelp: 'ExportOptions.plist', valueHelp: 'ExportOptions.plist',
// TODO(jmagman): Update help text with link to Flutter docs.
help: help:
'Export an IPA with these options. See "xcodebuild -h" for available exportOptionsPlist keys.', 'Optionally export an IPA with these options. See "xcodebuild -h" for available exportOptionsPlist keys.',
); );
} }
...@@ -94,7 +82,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -94,7 +82,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
final List<String> aliases = <String>['xcarchive']; final List<String> aliases = <String>['xcarchive'];
@override @override
final String description = 'Build an iOS archive bundle and IPA for distribution (Mac OS X host only).'; final String description = 'Build an iOS archive bundle (Mac OS X host only).';
@override @override
final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive; final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive;
...@@ -117,16 +105,9 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -117,16 +105,9 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
.childDirectory('Applications'); .childDirectory('Applications');
@override @override
Future<void> validateCommand() async { Future<FlutterCommandResult> runCommand() async {
final String? exportOptions = exportOptionsPlist; final String? exportOptions = exportOptionsPlist;
if (exportOptions != null) { if (exportOptions != null) {
if (argResults?.wasParsed('export-method') == true) {
throwToolExit(
'"--export-options-plist" is not compatible with "--export-method". Either use "--export-options-plist" and '
'a plist describing how the IPA should be exported by Xcode, or use "--export-method" to create a new plist.\n'
'See "xcodebuild -h" for available exportOptionsPlist keys.'
);
}
final FileSystemEntityType type = globals.fs.typeSync(exportOptions); final FileSystemEntityType type = globals.fs.typeSync(exportOptions);
if (type == FileSystemEntityType.notFound) { if (type == FileSystemEntityType.notFound) {
throwToolExit( throwToolExit(
...@@ -136,15 +117,14 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -136,15 +117,14 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
'"$exportOptions" is not a file. See "xcodebuild -h" for available keys.'); '"$exportOptions" is not a file. See "xcodebuild -h" for available keys.');
} }
} }
return super.validateCommand();
}
@override
Future<FlutterCommandResult> runCommand() async {
final FlutterCommandResult xcarchiveResult = await super.runCommand(); final FlutterCommandResult xcarchiveResult = await super.runCommand();
final BuildInfo buildInfo = await getBuildInfo(); final BuildInfo buildInfo = await getBuildInfo();
displayNullSafetyMode(buildInfo); displayNullSafetyMode(buildInfo);
if (exportOptions == null) {
return xcarchiveResult;
}
// xcarchive failed or not at expected location. // xcarchive failed or not at expected location.
if (xcarchiveResult.exitStatus != ExitStatus.success) { if (xcarchiveResult.exitStatus != ExitStatus.success) {
globals.printStatus('Skipping IPA'); globals.printStatus('Skipping IPA');
...@@ -155,20 +135,9 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -155,20 +135,9 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
final BuildableIOSApp app = await buildableIOSApp; final BuildableIOSApp app = await buildableIOSApp;
Status? status; Status? status;
RunResult? result; RunResult? result;
final String relativeOutputPath = app.ipaOutputPath; final String outputPath = globals.fs.path.absolute(app.ipaOutputPath);
final String absoluteOutputPath = globals.fs.path.absolute(relativeOutputPath);
final String absoluteArchivePath = globals.fs.path.absolute(app.archiveBundleOutputPath);
final String exportMethod = stringArg('export-method')!;
final bool isAppStoreUpload = exportMethod == 'app-store';
File? generatedExportPlist;
try { try {
final String exportMethodDisplayName = isAppStoreUpload ? 'App Store' : exportMethod; status = globals.logger.startProgress('Building IPA...');
status = globals.logger.startProgress('Building $exportMethodDisplayName IPA...');
String? exportOptions = exportOptionsPlist;
if (exportOptions == null) {
generatedExportPlist = _createExportPlist();
exportOptions = generatedExportPlist.path;
}
result = await globals.processUtils.run( result = await globals.processUtils.run(
<String>[ <String>[
...@@ -180,15 +149,14 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -180,15 +149,14 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
'-allowProvisioningUpdates', '-allowProvisioningUpdates',
], ],
'-archivePath', '-archivePath',
absoluteArchivePath, globals.fs.path.absolute(app.archiveBundleOutputPath),
'-exportPath', '-exportPath',
absoluteOutputPath, outputPath,
'-exportOptionsPlist', '-exportOptionsPlist',
globals.fs.path.absolute(exportOptions), globals.fs.path.absolute(exportOptions),
], ],
); );
} finally { } finally {
generatedExportPlist?.deleteSync();
status?.stop(); status?.stop();
} }
...@@ -203,68 +171,13 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { ...@@ -203,68 +171,13 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand {
LineSplitter.split(result.stderr) LineSplitter.split(result.stderr)
.where((String line) => line.contains('error: ')) .where((String line) => line.contains('error: '))
.forEach(errorMessage.writeln); .forEach(errorMessage.writeln);
throwToolExit('Encountered error while building IPA:\n$errorMessage');
globals.printError('Encountered error while creating the IPA:');
globals.printError(errorMessage.toString());
globals.printError('Try distributing the app in Xcode: "open $absoluteArchivePath"');
throwToolExit(null);
} }
globals.printStatus('Built IPA to $absoluteOutputPath.'); globals.printStatus('Built IPA to $outputPath.');
if (isAppStoreUpload) {
globals.printStatus('To upload to the App Store either:');
globals.printStatus(
'1. Drag and drop the "$relativeOutputPath/*.ipa" bundle into the Apple Transport macOS app https://apps.apple.com/us/app/transporter/id1450874784',
indent: 4,
);
globals.printStatus(
'2. Run "xcrun altool --upload-app --type ios -f $relativeOutputPath/*.ipa --apiKey your_api_key --apiIssuer your_issuer_id".',
indent: 4,
);
globals.printStatus(
'See "man altool" for details about how to authenticate with the App Store Connect API key.',
indent: 7,
);
}
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
File _createExportPlist() {
// Create the plist to be passed into xcodebuild -exportOptionsPlist.
final StringBuffer plistContents = StringBuffer('''
<?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>method</key>
''');
plistContents.write('''
<string>${stringArg('export-method')}</string>
''');
if (xcodeBuildResult?.xcodeBuildExecution?.buildSettings['ENABLE_BITCODE'] != 'YES') {
// Bitcode is off by default in Flutter iOS apps.
plistContents.write('''
<key>uploadBitcode</key>
<false/>
</dict>
</plist>
''');
} else {
plistContents.write('''
</dict>
</plist>
''');
}
final File tempPlist = globals.fs.systemTempDirectory
.createTempSync('flutter_build_ios.').childFile('ExportOptions.plist');
tempPlist.writeAsStringSync(plistContents.toString());
return tempPlist;
}
} }
abstract class _BuildIOSSubCommand extends BuildSubCommand { abstract class _BuildIOSSubCommand extends BuildSubCommand {
...@@ -295,10 +208,6 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { ...@@ -295,10 +208,6 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
}; };
XcodeBuildAction get xcodeBuildAction; XcodeBuildAction get xcodeBuildAction;
/// The result of the Xcode build command. Null until it finishes.
@protected
XcodeBuildResult? xcodeBuildResult;
EnvironmentType get environmentType; EnvironmentType get environmentType;
bool get configOnly; bool get configOnly;
bool get shouldCodesign; bool get shouldCodesign;
...@@ -362,7 +271,6 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { ...@@ -362,7 +271,6 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand {
buildAction: xcodeBuildAction, buildAction: xcodeBuildAction,
deviceID: globals.deviceManager?.specifiedDeviceId, deviceID: globals.deviceManager?.specifiedDeviceId,
); );
xcodeBuildResult = result;
if (!result.success) { if (!result.success) {
await diagnoseXcodeBuildFailure(result, globals.flutterUsage, globals.logger); await diagnoseXcodeBuildFailure(result, globals.flutterUsage, globals.logger);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
...@@ -17,14 +18,9 @@ import 'package:flutter_tools/src/reporting/reporting.dart'; ...@@ -17,14 +18,9 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
import '../../general.shard/ios/xcresult_test_data.dart'; import '../../general.shard/ios/xcresult_test_data.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/test_flutter_command_runner.dart'; import '../../src/test_flutter_command_runner.dart';
class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInterpreter { class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInterpreter {
FakeXcodeProjectInterpreterWithBuildSettings({ this.overrides = const <String, String>{} });
final Map<String, String> overrides;
@override @override
Future<Map<String, String>> getBuildSettings( Future<Map<String, String>> getBuildSettings(
String projectPath, { String projectPath, {
...@@ -32,7 +28,6 @@ class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInter ...@@ -32,7 +28,6 @@ class FakeXcodeProjectInterpreterWithBuildSettings extends FakeXcodeProjectInter
Duration timeout = const Duration(minutes: 1), Duration timeout = const Duration(minutes: 1),
}) async { }) async {
return <String, String>{ return <String, String>{
...overrides,
'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject', 'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
'DEVELOPMENT_TEAM': 'abc', 'DEVELOPMENT_TEAM': 'abc',
}; };
...@@ -55,7 +50,7 @@ final Platform notMacosPlatform = FakePlatform( ...@@ -55,7 +50,7 @@ final Platform notMacosPlatform = FakePlatform(
void main() { void main() {
FileSystem fileSystem; FileSystem fileSystem;
TestUsage usage; TestUsage usage;
FakeProcessManager fakeProcessManager; BufferLogger logger;
setUpAll(() { setUpAll(() {
Cache.disableLocking(); Cache.disableLocking();
...@@ -64,7 +59,7 @@ void main() { ...@@ -64,7 +59,7 @@ void main() {
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
usage = TestUsage(); usage = TestUsage();
fakeProcessManager = FakeProcessManager.empty(); logger = BufferLogger.test();
}); });
// Sets up the minimal mock project files necessary to look like a Flutter project. // Sets up the minimal mock project files necessary to look like a Flutter project.
...@@ -132,33 +127,21 @@ void main() { ...@@ -132,33 +127,21 @@ void main() {
); );
} }
FakeCommand _exportArchiveCommand({ const FakeCommand exportArchiveCommand = FakeCommand(
String exportOptionsPlist = '/ExportOptions.plist', command: <String>[
File cachePlist, 'xcrun',
}) { 'xcodebuild',
return FakeCommand( '-exportArchive',
command: <String>[ '-allowProvisioningDeviceRegistration',
'xcrun', '-allowProvisioningUpdates',
'xcodebuild', '-archivePath',
'-exportArchive', '/build/ios/archive/Runner.xcarchive',
'-allowProvisioningDeviceRegistration', '-exportPath',
'-allowProvisioningUpdates', '/build/ios/ipa',
'-archivePath', '-exportOptionsPlist',
'/build/ios/archive/Runner.xcarchive', '/ExportOptions.plist'
'-exportPath', ],
'/build/ios/ipa', );
'-exportOptionsPlist',
exportOptionsPlist,
],
onRun: () {
// exportOptionsPlist will be cleaned up within the command.
// Save it somewhere else so test expectations can be run on it.
if (cachePlist != null) {
cachePlist.writeAsStringSync(fileSystem.file(_exportOptionsPlist).readAsStringSync());
}
}
);
}
testUsingContext('ipa build fails when there is no ios project', () async { testUsingContext('ipa build fails when there is no ios project', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
...@@ -252,167 +235,37 @@ void main() { ...@@ -252,167 +235,37 @@ void main() {
FakeXcodeProjectInterpreterWithBuildSettings(), FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('ipa build fails when --export-options-plist and --export-method are used together', () async { testUsingContext('ipa build invokes xcode build', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectToolExitLater(
createTestCommandRunner(command).run(<String>[
'build',
'ipa',
'--export-options-plist',
'ExportOptions.plist',
'--export-method',
'app-store',
'--no-pub',
]),
contains('"--export-options-plist" is not compatible with "--export-method"'),
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
Platform: () => macosPlatform,
XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('ipa build invokes xcodebuild and archives for app store', () async {
final File cachedExportOptionsPlist = fileSystem.file('/CachedExportOptions.plist');
final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(),
_exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist, cachePlist: cachedExportOptionsPlist),
]);
_createMinimalMockProjectFiles();
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub'] const <String>['build', 'ipa', '--no-pub']
); );
const String expectedIpaPlistContents = '''
<?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>method</key>
<string>app-store</string>
<key>uploadBitcode</key>
<false/>
</dict>
</plist>
''';
final String actualIpaPlistContents = fileSystem.file(cachedExportOptionsPlist).readAsStringSync();
expect(actualIpaPlistContents, expectedIpaPlistContents);
expect(testLogger.statusText, contains('build/ios/archive/Runner.xcarchive')); expect(testLogger.statusText, contains('build/ios/archive/Runner.xcarchive'));
expect(testLogger.statusText, contains('Building App Store IPA'));
expect(testLogger.statusText, contains('Built IPA to /build/ios/ipa'));
expect(testLogger.statusText, contains('To upload to the App Store'));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
});
testUsingContext('ipa build invokes xcodebuild and archives for ad-hoc distribution', () async {
final File cachedExportOptionsPlist = fileSystem.file('/CachedExportOptions.plist');
final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand, xattrCommand,
_setUpFakeXcodeBuildHandler(), _setUpFakeXcodeBuildHandler(),
_exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist, cachePlist: cachedExportOptionsPlist), ]),
]);
_createMinimalMockProjectFiles();
await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '--export-method', 'ad-hoc']
);
const String expectedIpaPlistContents = '''
<?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>method</key>
<string>ad-hoc</string>
<key>uploadBitcode</key>
<false/>
</dict>
</plist>
''';
final String actualIpaPlistContents = fileSystem.file(cachedExportOptionsPlist).readAsStringSync();
expect(actualIpaPlistContents, expectedIpaPlistContents);
expect(testLogger.statusText, contains('build/ios/archive/Runner.xcarchive'));
expect(testLogger.statusText, contains('Building ad-hoc IPA'));
expect(testLogger.statusText, contains('Built IPA to /build/ios/ipa'));
// Don't instruct how to upload to the App Store.
expect(testLogger.statusText, isNot(contains('To upload')));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('ipa build invokes xcodebuild and archives with bitcode on', () async {
final File cachedExportOptionsPlist = fileSystem.file('/CachedExportOptions.plist');
final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(),
_exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist, cachePlist: cachedExportOptionsPlist),
]);
_createMinimalMockProjectFiles();
await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub',]
);
const String expectedIpaPlistContents = '''
<?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>method</key>
<string>app-store</string>
</dict>
</plist>
''';
final String actualIpaPlistContents = fileSystem.file(cachedExportOptionsPlist).readAsStringSync();
expect(actualIpaPlistContents, expectedIpaPlistContents);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(
overrides: <String, String>{'ENABLE_BITCODE': 'YES'},
),
});
testUsingContext('ipa build invokes xcode build with verbosity', () async { testUsingContext('ipa build invokes xcode build with verbosity', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(verbose: true),
_exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '-v'] const <String>['build', 'ipa', '--no-pub', '-v']
); );
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(verbose: true),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
...@@ -442,7 +295,19 @@ void main() { ...@@ -442,7 +295,19 @@ void main() {
fileSystem.file('build/ios/archive/Runner.xcarchive/Products/Applications/Runner.app/Frameworks/App.framework/App') fileSystem.file('build/ios/archive/Runner.xcarchive/Products/Applications/Runner.app/Frameworks/App.framework/App')
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsBytesSync(List<int>.generate(10000, (int index) => 0)); ..writeAsBytesSync(List<int>.generate(10000, (int index) => 0));
fakeProcessManager.addCommands(<FakeCommand>[
await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your iOS bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'ios'),
));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand, xattrCommand,
_setUpFakeXcodeBuildHandler(onRun: () { _setUpFakeXcodeBuildHandler(onRun: () {
fileSystem.file('build/flutter_size_01/snapshot.arm64.json') fileSystem.file('build/flutter_size_01/snapshot.arm64.json')
...@@ -460,39 +325,19 @@ void main() { ...@@ -460,39 +325,19 @@ void main() {
..createSync(recursive: true) ..createSync(recursive: true)
..writeAsStringSync('{}'); ..writeAsStringSync('{}');
}), }),
_exportArchiveCommand(exportOptionsPlist: _exportOptionsPlist), ]),
]);
await createTestCommandRunner(command).run(
const <String>['build', 'ipa', '--no-pub', '--analyze-size']
);
expect(testLogger.statusText, contains('A summary of your iOS bundle analysis can be found at'));
expect(testLogger.statusText, contains('flutter pub global activate devtools; flutter pub global run devtools --appSizeBase='));
expect(usage.events, contains(
const TestUsageEvent('code-size-analysis', 'ios'),
));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager,
Platform: () => macosPlatform, Platform: () => macosPlatform,
FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform), FileSystemUtils: () => FileSystemUtils(fileSystem: fileSystem, platform: macosPlatform),
Usage: () => usage, Usage: () => usage,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('ipa build invokes xcode build export archive when passed plist', () async { testUsingContext('ipa build invokes xcode build export archive', () async {
final String outputPath = final String outputPath =
fileSystem.path.absolute(fileSystem.path.join('build', 'ios', 'ipa')); fileSystem.path.absolute(fileSystem.path.join('build', 'ios', 'ipa'));
final File exportOptions = fileSystem.file('ExportOptions.plist') final File exportOptions = fileSystem.file('ExportOptions.plist')
..createSync(); ..createSync();
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(),
_exportArchiveCommand(),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
...@@ -505,25 +350,23 @@ void main() { ...@@ -505,25 +350,23 @@ void main() {
], ],
); );
expect(testLogger.statusText, contains('Built IPA to $outputPath.')); expect(logger.statusText, contains('Built IPA to $outputPath.'));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(),
exportArchiveCommand,
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
Logger: () => logger,
XcodeProjectInterpreter: () => XcodeProjectInterpreter: () =>
FakeXcodeProjectInterpreterWithBuildSettings(), FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('Trace error if xcresult is empty.', () async { testUsingContext('Trace error if xcresult is empty.', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectLater( await expectLater(
...@@ -532,23 +375,22 @@ void main() { ...@@ -532,23 +375,22 @@ void main() {
); );
expect(testLogger.traceText, contains('xcresult parser: Unrecognized top level json format.')); expect(testLogger.traceText, contains('xcresult parser: Unrecognized top level json format.'));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('Display xcresult issues on console if parsed.', () async { testUsingContext('Display xcresult issues on console if parsed.', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectLater( await expectLater(
...@@ -558,23 +400,22 @@ void main() { ...@@ -558,23 +400,22 @@ void main() {
expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'")); expect(testLogger.errorText, contains("Use of undeclared identifier 'asdas'"));
expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56')); expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('Do not display xcresult issues that needs to be discarded.', () async { testUsingContext('Do not display xcresult issues that needs to be discarded.', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithIssuesToBeDiscarded),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectLater( await expectLater(
...@@ -586,20 +427,22 @@ void main() { ...@@ -586,20 +427,22 @@ void main() {
expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56')); expect(testLogger.errorText, contains('/Users/m/Projects/test_create/ios/Runner/AppDelegate.m:7:56'));
expect(testLogger.errorText, isNot(contains('Command PhaseScriptExecution failed with a nonzero exit code'))); expect(testLogger.errorText, isNot(contains('Command PhaseScriptExecution failed with a nonzero exit code')));
expect(testLogger.warningText, isNot(contains("The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99."))); expect(testLogger.warningText, isNot(contains("The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.")));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithIssuesToBeDiscarded),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
testUsingContext('Trace if xcresult bundle does not exist.', () async { testUsingContext('Trace if xcresult bundle does not exist.', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectLater( await expectLater(
...@@ -608,10 +451,13 @@ void main() { ...@@ -608,10 +451,13 @@ void main() {
); );
expect(testLogger.traceText, contains('The xcresult bundle are not generated. Displaying xcresult is disabled.')); expect(testLogger.traceText, contains('The xcresult bundle are not generated. Displaying xcresult is disabled.'));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1),
_setUpXCResultCommand(stdout: kSampleResultJsonWithIssues),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
...@@ -619,13 +465,7 @@ void main() { ...@@ -619,13 +465,7 @@ void main() {
testUsingContext('Extra error message for provision profile issue in xcresulb bundle.', () async { testUsingContext('Extra error message for provision profile issue in xcresulb bundle.', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
fakeProcessManager.addCommands(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithProvisionIssue),
]);
_createMinimalMockProjectFiles(); _createMinimalMockProjectFiles();
await expectLater( await expectLater(
...@@ -638,14 +478,18 @@ void main() { ...@@ -638,14 +478,18 @@ void main() {
expect(testLogger.errorText, contains('Verify that the Bundle Identifier in your project is your signing id in Xcode')); expect(testLogger.errorText, contains('Verify that the Bundle Identifier in your project is your signing id in Xcode'));
expect(testLogger.errorText, contains('open ios/Runner.xcworkspace')); expect(testLogger.errorText, contains('open ios/Runner.xcworkspace'));
expect(testLogger.errorText, contains("Also try selecting 'Product > Build' to fix the problem:")); expect(testLogger.errorText, contains("Also try selecting 'Product > Build' to fix the problem:"));
expect(fakeProcessManager, hasNoRemainingExpectations);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => fakeProcessManager, ProcessManager: () => FakeProcessManager.list(<FakeCommand>[
xattrCommand,
_setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () {
fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync();
}),
_setUpXCResultCommand(stdout: kSampleResultJsonWithProvisionIssue),
]),
Platform: () => macosPlatform, Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(),
}); });
} }
const String _xcBundleFilePath = '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle'; const String _xcBundleFilePath = '/.tmp_rand0/flutter_ios_build_temp_dirrand0/temporary_xcresult_bundle';
const String _exportOptionsPlist = '/.tmp_rand0/flutter_build_ios.rand0/ExportOptions.plist';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment