Allow `--use-application-binary` using app-bundles on ios (#17691)
This makes it easier to run ios add2app apps with Flutter run.
diff --git a/packages/flutter_tools/test/application_package_test.dart b/packages/flutter_tools/test/application_package_test.dart
index a2f4856..d4e4ded 100644
--- a/packages/flutter_tools/test/application_package_test.dart
+++ b/packages/flutter_tools/test/application_package_test.dart
@@ -2,15 +2,25 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:flutter_tools/src/application_package.dart';
-import 'package:test/test.dart';
+import 'dart:convert';
+import 'package:flutter_tools/src/application_package.dart';
+import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/os.dart';
+import 'package:flutter_tools/src/ios/ios_workflow.dart';
+import 'package:test/test.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:mockito/mockito.dart';
import 'src/context.dart';
void main() {
group('ApkManifestData', () {
testUsingContext('parse sdk', () {
- final ApkManifestData data = ApkManifestData.parseFromAaptBadging(_aaptData);
+ final ApkManifestData data =
+ ApkManifestData.parseFromAaptBadging(_aaptData);
expect(data, isNotNull);
expect(data.packageName, 'io.flutter.gallery');
expect(data.launchableActivityName, 'io.flutter.app.FlutterActivity');
@@ -28,6 +38,118 @@
expect(buildableIOSApp.isSwift, true);
});
});
+
+ group('PrebuiltIOSApp', () {
+ final Map<Type, Generator> overrides = <Type, Generator>{
+ FileSystem: () => new MemoryFileSystem(),
+ IOSWorkflow: () => new MockIosWorkFlow()
+ };
+ testUsingContext('Error on non-existing file', () {
+ final PrebuiltIOSApp iosApp =
+ new IOSApp.fromPrebuiltApp('not_existing.ipa');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(
+ logger.errorText,
+ 'File "not_existing.ipa" does not exist. Use an app bundle or an ipa.\n',
+ );
+ }, overrides: overrides);
+ testUsingContext('Error on non-app-bundle folder', () {
+ fs.directory('regular_folder').createSync();
+ final PrebuiltIOSApp iosApp =
+ new IOSApp.fromPrebuiltApp('regular_folder');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(
+ logger.errorText, 'Folder "regular_folder" is not an app bundle.\n');
+ }, overrides: overrides);
+ testUsingContext('Error on no info.plist', () {
+ fs.directory('bundle.app').createSync();
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(
+ logger.errorText,
+ 'Invalid prebuilt iOS app. Does not contain Info.plist.\n',
+ );
+ }, overrides: overrides);
+ testUsingContext('Error on bad info.plist', () {
+ fs.directory('bundle.app').createSync();
+ fs.file('bundle.app/Info.plist').writeAsStringSync(badPlistData);
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(
+ logger.errorText,
+ contains(
+ 'Invalid prebuilt iOS app. Info.plist does not contain bundle identifier\n'),
+ );
+ }, overrides: overrides);
+ testUsingContext('Success with app bundle', () {
+ fs.directory('bundle.app').createSync();
+ fs.file('bundle.app/Info.plist').writeAsStringSync(plistData);
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('bundle.app');
+ final BufferLogger logger = context[Logger];
+ expect(logger.errorText, isEmpty);
+ expect(iosApp.bundleDir.path, 'bundle.app');
+ expect(iosApp.id, 'fooBundleId');
+ expect(iosApp.bundleName, 'bundle.app');
+ }, overrides: overrides);
+ testUsingContext('Bad ipa zip-file, no payload dir', () {
+ fs.file('app.ipa').createSync();
+ when(os.unzip(fs.file('app.ipa'), any)).thenAnswer((Invocation _) {});
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(
+ logger.errorText,
+ 'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.\n',
+ );
+ }, overrides: overrides);
+ testUsingContext('Bad ipa zip-file, two app bundles', () {
+ fs.file('app.ipa').createSync();
+ when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
+ final File zipFile = invocation.positionalArguments[0];
+ if (zipFile.path != 'app.ipa') {
+ return null;
+ }
+ final Directory targetDirectory = invocation.positionalArguments[1];
+ final String bundlePath1 =
+ fs.path.join(targetDirectory.path, 'Payload', 'bundle1.app');
+ final String bundlePath2 =
+ fs.path.join(targetDirectory.path, 'Payload', 'bundle2.app');
+ fs.directory(bundlePath1).createSync(recursive: true);
+ fs.directory(bundlePath2).createSync(recursive: true);
+ });
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
+ expect(iosApp, isNull);
+ final BufferLogger logger = context[Logger];
+ expect(logger.errorText,
+ 'Invalid prebuilt iOS ipa. Does not contain a single app bundle.\n');
+ }, overrides: overrides);
+ testUsingContext('Success with ipa', () {
+ fs.file('app.ipa').createSync();
+ when(os.unzip(any, any)).thenAnswer((Invocation invocation) {
+ final File zipFile = invocation.positionalArguments[0];
+ if (zipFile.path != 'app.ipa') {
+ return null;
+ }
+ final Directory targetDirectory = invocation.positionalArguments[1];
+ final Directory bundleAppDir = fs.directory(
+ fs.path.join(targetDirectory.path, 'Payload', 'bundle.app'));
+ bundleAppDir.createSync(recursive: true);
+ fs
+ .file(fs.path.join(bundleAppDir.path, 'Info.plist'))
+ .writeAsStringSync(plistData);
+ });
+ final PrebuiltIOSApp iosApp = new IOSApp.fromPrebuiltApp('app.ipa');
+ final BufferLogger logger = context[Logger];
+ expect(logger.errorText, isEmpty);
+ expect(iosApp.bundleDir.path, endsWith('bundle.app'));
+ expect(iosApp.id, 'fooBundleId');
+ expect(iosApp.bundleName, 'bundle.app');
+ }, overrides: overrides);
+ });
}
const String _aaptData = '''
@@ -69,3 +191,23 @@
'SWIFT_OPTIMIZATION_LEVEL': '-Onone',
'SWIFT_VERSION': '3.0',
};
+
+class MockIosWorkFlow extends Mock implements IOSWorkflow {
+ @override
+ String getPlistValueFromFile(String path, String key) {
+ final File file = fs.file(path);
+ if (!file.existsSync()) {
+ return null;
+ }
+ return json.decode(file.readAsStringSync())[key];
+ }
+}
+
+// Contains no bundle identifier.
+const String badPlistData = '''
+{}
+''';
+
+const String plistData = '''
+{"CFBundleIdentifier": "fooBundleId"}
+''';