Add support for --use-application-binary on iOS (#6318)
Fixes #6283
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 233836a..dea394a 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -205,6 +205,9 @@
printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
}
+ // Run shutdown hooks before flushing logs
+ await runShutdownHooks();
+
// Write any buffered output.
logger.flush();
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index cf8ae2b..d7a68cf 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -8,10 +8,11 @@
import 'package:xml/xml.dart' as xml;
import 'android/gradle.dart';
+import 'base/os.dart' show os;
import 'base/process.dart';
import 'build_info.dart';
import 'globals.dart';
-import 'ios/plist_utils.dart';
+import 'ios/plist_utils.dart' as plist;
import 'ios/xcodeproj.dart';
abstract class ApplicationPackage {
@@ -122,41 +123,82 @@
String get name => path.basename(apkPath);
}
-class IOSApp extends ApplicationPackage {
- static final String kBundleName = 'Runner.app';
+/// Tests whether a [FileSystemEntity] is an iOS bundle directory
+bool _isBundleDirectory(FileSystemEntity entity) =>
+ entity is Directory && entity.path.endsWith('.app');
- IOSApp({
- this.appDirectory,
- String projectBundleId
- }) : super(id: projectBundleId);
+abstract class IOSApp extends ApplicationPackage {
+ IOSApp({String projectBundleId}) : super(id: projectBundleId);
+
+ /// Creates a new IOSApp from an existing IPA.
+ factory IOSApp.fromIpa(String applicationBinary) {
+ Directory bundleDir;
+ try {
+ Directory tempDir = Directory.systemTemp.createTempSync('flutter_app_');
+ addShutdownHook(() async => await tempDir.delete(recursive: true));
+ os.unzip(new File(applicationBinary), tempDir);
+ Directory payloadDir = new Directory(path.join(tempDir.path, 'Payload'));
+ bundleDir = payloadDir.listSync().singleWhere(_isBundleDirectory);
+ } on StateError catch (e, stackTrace) {
+ printError('Invalid prebuilt iOS binary: ${e.toString()}', stackTrace);
+ return null;
+ }
+
+ String plistPath = path.join(bundleDir.path, 'Info.plist');
+ String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey);
+ if (id == null)
+ return null;
+
+ return new PrebuiltIOSApp(
+ ipaPath: applicationBinary,
+ bundleDir: bundleDir,
+ bundleName: path.basename(bundleDir.path),
+ projectBundleId: id,
+ );
+ }
factory IOSApp.fromCurrentDirectory() {
if (getCurrentHostPlatform() != HostPlatform.darwin_x64)
return null;
String plistPath = path.join('ios', 'Runner', 'Info.plist');
- String value = getValueFromFile(plistPath, kCFBundleIdentifierKey);
- if (value == null)
+ String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey);
+ if (id == null)
return null;
String projectPath = path.join('ios', 'Runner.xcodeproj');
- value = substituteXcodeVariables(value, projectPath, 'Runner');
+ id = substituteXcodeVariables(id, projectPath, 'Runner');
- return new IOSApp(
+ return new BuildableIOSApp(
appDirectory: path.join('ios'),
- projectBundleId: value
+ projectBundleId: id
);
}
@override
+ String get displayName => id;
+
+ String get simulatorBundlePath;
+
+ String get deviceBundlePath;
+}
+
+class BuildableIOSApp extends IOSApp {
+ static final String kBundleName = 'Runner.app';
+
+ BuildableIOSApp({
+ this.appDirectory,
+ String projectBundleId,
+ }) : super(projectBundleId: projectBundleId);
+
+ final String appDirectory;
+
+ @override
String get name => kBundleName;
@override
- String get displayName => id;
-
- final String appDirectory;
-
String get simulatorBundlePath => _buildAppPath('iphonesimulator');
+ @override
String get deviceBundlePath => _buildAppPath('iphoneos');
String _buildAppPath(String type) {
@@ -164,6 +206,30 @@
}
}
+class PrebuiltIOSApp extends IOSApp {
+ final String ipaPath;
+ final Directory bundleDir;
+ final String bundleName;
+
+ PrebuiltIOSApp({
+ this.ipaPath,
+ this.bundleDir,
+ this.bundleName,
+ String projectBundleId,
+ }) : super(projectBundleId: projectBundleId);
+
+ @override
+ String get name => bundleName;
+
+ @override
+ String get simulatorBundlePath => _bundlePath;
+
+ @override
+ String get deviceBundlePath => _bundlePath;
+
+ String get _bundlePath => bundleDir.path;
+}
+
ApplicationPackage getApplicationPackageForPlatform(TargetPlatform platform, {
String applicationBinary
}) {
@@ -171,11 +237,13 @@
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
- if (applicationBinary != null)
- return new AndroidApk.fromApk(applicationBinary);
- return new AndroidApk.fromCurrentDirectory();
+ return applicationBinary == null
+ ? new AndroidApk.fromCurrentDirectory()
+ : new AndroidApk.fromApk(applicationBinary);
case TargetPlatform.ios:
- return new IOSApp.fromCurrentDirectory();
+ return applicationBinary == null
+ ? new IOSApp.fromCurrentDirectory()
+ : new IOSApp.fromIpa(applicationBinary);
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
return null;
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 2dd7716..9fcd407 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -9,9 +9,20 @@
import '../globals.dart';
typedef String StringConverter(String string);
+typedef Future<dynamic> ShutdownHook();
// TODO(ianh): We have way too many ways to run subprocesses in this project.
+List<ShutdownHook> _shutdownHooks = <ShutdownHook>[];
+void addShutdownHook(ShutdownHook shutdownHook) {
+ _shutdownHooks.add(shutdownHook);
+}
+
+Future<Null> runShutdownHooks() async {
+ for (ShutdownHook shutdownHook in _shutdownHooks)
+ await shutdownHook();
+}
+
Map<String, String> _environment(bool allowReentrantFlutter, [Map<String, String> environment]) {
if (allowReentrantFlutter) {
if (environment == null)
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index ea8de62..9f510c5 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -184,17 +184,19 @@
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
}) async {
- // TODO(chinmaygarde): Use checked, mainPath, route.
- // TODO(devoncarew): Handle startPaused, debugPort.
- printTrace('Building ${app.name} for $id');
+ if (!prebuiltApplication) {
+ // TODO(chinmaygarde): Use checked, mainPath, route.
+ // TODO(devoncarew): Handle startPaused, debugPort.
+ printTrace('Building ${app.name} for $id');
- // Step 1: Install the precompiled/DBC application if necessary.
- XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: mode, target: mainPath, buildForDevice: true);
- if (!buildResult.success) {
- printError('Could not build the precompiled application for the device.');
- diagnoseXcodeBuildFailure(buildResult);
- printError('');
- return new LaunchResult.failed();
+ // Step 1: Build the precompiled/DBC application if necessary.
+ XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: mode, target: mainPath, buildForDevice: true);
+ if (!buildResult.success) {
+ printError('Could not build the precompiled application for the device.');
+ diagnoseXcodeBuildFailure(buildResult);
+ printError('');
+ return new LaunchResult.failed();
+ }
}
// Step 2: Check that the application exists at the specified path.
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 9016171..f217e12 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -100,7 +100,7 @@
}
Future<XcodeBuildResult> buildXcodeProject({
- IOSApp app,
+ BuildableIOSApp app,
BuildMode mode,
String target: flx.defaultMainPath,
bool buildForDevice,
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 4b18195..bab6f10 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -416,10 +416,12 @@
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
}) async {
- printTrace('Building ${app.name} for $id.');
+ if (!prebuiltApplication) {
+ printTrace('Building ${app.name} for $id.');
- if (!(await _setupUpdatedApplicationBundle(app)))
- return new LaunchResult.failed();
+ if (!(await _setupUpdatedApplicationBundle(app)))
+ return new LaunchResult.failed();
+ }
ProtocolDiscovery observatoryDiscovery;
@@ -427,11 +429,15 @@
observatoryDiscovery = new ProtocolDiscovery(logReader, ProtocolDiscovery.kObservatoryService);
// Prepare launch arguments.
- List<String> args = <String>[
- "--flx=${path.absolute(path.join(getBuildDirectory(), 'app.flx'))}",
- "--dart-main=${path.absolute(mainPath)}",
- "--packages=${path.absolute('.packages')}",
- ];
+ List<String> args = <String>[];
+
+ if (!prebuiltApplication) {
+ args.addAll(<String>[
+ "--flx=${path.absolute(path.join(getBuildDirectory(), 'app.flx'))}",
+ "--dart-main=${path.absolute(mainPath)}",
+ "--packages=${path.absolute('.packages')}",
+ ]);
+ }
if (debuggingOptions.debuggingEnabled) {
if (debuggingOptions.buildMode == BuildMode.debug)
diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart
index aff9ea4..642465135 100644
--- a/packages/flutter_tools/lib/src/run.dart
+++ b/packages/flutter_tools/lib/src/run.dart
@@ -148,7 +148,7 @@
}
// TODO(devoncarew): This fails for ios devices - we haven't built yet.
- if (device is AndroidDevice) {
+ if (prebuiltMode || device is AndroidDevice) {
printTrace('Running install command.');
if (!(installApp(device, _package, uninstall: false)))
return 1;