Commit 904d5243 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Add support for --use-application-binary on iOS (#6318)

Fixes #6283
parent 6e5c192c
......@@ -205,6 +205,9 @@ Future<Null> _exit(int code) async {
printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
}
// Run shutdown hooks before flushing logs
await runShutdownHooks();
// Write any buffered output.
logger.flush();
......
......@@ -8,10 +8,11 @@ import 'package:path/path.dart' as path;
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 @@ class AndroidApk extends ApplicationPackage {
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');
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;
}
IOSApp({
this.appDirectory,
String projectBundleId
}) : super(id: projectBundleId);
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 name => kBundleName;
@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 simulatorBundlePath => _buildAppPath('iphonesimulator');
@override
String get deviceBundlePath => _buildAppPath('iphoneos');
String _buildAppPath(String type) {
......@@ -164,6 +206,30 @@ class IOSApp extends ApplicationPackage {
}
}
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 @@ ApplicationPackage getApplicationPackageForPlatform(TargetPlatform platform, {
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;
......
......@@ -9,9 +9,20 @@ import 'dart:io';
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)
......
......@@ -184,17 +184,19 @@ class IOSDevice extends Device {
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');
// 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();
if (!prebuiltApplication) {
// TODO(chinmaygarde): Use checked, mainPath, route.
// TODO(devoncarew): Handle startPaused, debugPort.
printTrace('Building ${app.name} for $id');
// 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.
......
......@@ -100,7 +100,7 @@ bool _xcodeVersionCheckValid(int major, int minor) {
}
Future<XcodeBuildResult> buildXcodeProject({
IOSApp app,
BuildableIOSApp app,
BuildMode mode,
String target: flx.defaultMainPath,
bool buildForDevice,
......
......@@ -416,10 +416,12 @@ class IOSSimulator extends Device {
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 @@ class IOSSimulator extends Device {
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)
......
......@@ -148,7 +148,7 @@ class RunAndStayResident extends ResidentRunner {
}
// 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;
......
......@@ -21,7 +21,7 @@ class MockApplicationPackageStore extends ApplicationPackageStore {
apkPath: '/mock/path/to/android/SkyShell.apk',
launchActivity: 'io.flutter.android.mock.MockActivity'
),
iOS: new IOSApp(
iOS: new BuildableIOSApp(
appDirectory: '/mock/path/to/iOS/SkyShell.app',
projectBundleId: 'io.flutter.ios.mock'
)
......
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