Commit 477530f3 authored by Chinmay Garde's avatar Chinmay Garde

Merge pull request #1249 from chinmaygarde/master

First take on `flutter start` for iOS devices
parents 32078052 c8672a40
...@@ -47,11 +47,11 @@ class AndroidApk extends ApplicationPackage { ...@@ -47,11 +47,11 @@ class AndroidApk extends ApplicationPackage {
} }
class IOSApp extends ApplicationPackage { class IOSApp extends ApplicationPackage {
static const String _defaultName = 'SkyShell.app'; static const String _defaultId = 'io.flutter.runner.Runner';
static const String _defaultId = 'com.google.SkyShell'; static const String _defaultPath = 'ios';
IOSApp({ IOSApp({
String localPath, String localPath: _defaultPath,
String id: _defaultId String id: _defaultId
}) : super(localPath: localPath, id: id); }) : super(localPath: localPath, id: id);
} }
...@@ -98,14 +98,12 @@ class ApplicationPackageStore { ...@@ -98,14 +98,12 @@ class ApplicationPackageStore {
case TargetPlatform.iOS: case TargetPlatform.iOS:
assert(iOS == null); assert(iOS == null);
assert(config.type != BuildType.prebuilt); iOS = new IOSApp();
iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
case TargetPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null); assert(iOSSimulator == null);
assert(config.type != BuildType.prebuilt); iOSSimulator = new IOSApp();
iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
case TargetPlatform.mac: case TargetPlatform.mac:
......
...@@ -9,6 +9,7 @@ import 'package:path/path.dart' as path; ...@@ -9,6 +9,7 @@ import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../base/logging.dart'; import '../base/logging.dart';
import '../build_configuration.dart';
import '../device.dart'; import '../device.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'build.dart'; import 'build.dart';
...@@ -84,22 +85,30 @@ abstract class StartCommandBase extends FlutterCommand { ...@@ -84,22 +85,30 @@ abstract class StartCommandBase extends FlutterCommand {
continue; continue;
logging.fine('Running build command for $device.'); logging.fine('Running build command for $device.');
BuildCommand builder = new BuildCommand();
builder.inheritFromParent(this); if (device.platform == TargetPlatform.android) {
await builder.buildInTempDir( BuildCommand builder = new BuildCommand();
mainPath: mainPath, builder.inheritFromParent(this);
onBundleAvailable: (String localBundlePath) { await builder.buildInTempDir(
logging.fine('Starting bundle for $device.'); mainPath: mainPath,
final AndroidDevice androidDevice = device; // https://github.com/flutter/flutter/issues/1035 onBundleAvailable: (String localBundlePath) {
if (androidDevice.startBundle(package, localBundlePath, logging.fine('Starting bundle for $device.');
poke: poke, final AndroidDevice androidDevice = device; // https://github.com/flutter/flutter/issues/1035
checked: argResults['checked'], if (androidDevice.startBundle(package, localBundlePath,
traceStartup: argResults['trace-startup'], poke: poke,
route: argResults['route'], checked: argResults['checked'],
clearLogs: clearLogs)) traceStartup: argResults['trace-startup'],
startedSomething = true; route: argResults['route'],
clearLogs: clearLogs))
startedSomething = true;
}
);
} else {
bool result = await device.startApp(package);
if (!result) {
logging.severe('Could not start \'${package.name}\' on \'${device.id}\'');
} }
); }
} }
if (!startedSomething) { if (!startedSomething) {
......
...@@ -95,8 +95,7 @@ class IOSDevice extends Device { ...@@ -95,8 +95,7 @@ class IOSDevice extends Device {
'To copy files to iOS devices, please install ios-deploy. ' 'To copy files to iOS devices, please install ios-deploy. '
'You can do this using homebrew as follows:\n' 'You can do this using homebrew as follows:\n'
'\$ brew tap flutter/flutter\n' '\$ brew tap flutter/flutter\n'
'\$ brew install ios-deploy', '\$ brew install ios-deploy');
'Copying files to iOS devices is not currently supported on Linux.');
} }
static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) { static List<IOSDevice> getAttachedDevices([IOSDevice mockIOS]) {
...@@ -178,7 +177,7 @@ class IOSDevice extends Device { ...@@ -178,7 +177,7 @@ class IOSDevice extends Device {
@override @override
bool isAppInstalled(ApplicationPackage app) { bool isAppInstalled(ApplicationPackage app) {
try { try {
String apps = runCheckedSync([installerPath, '-l']); String apps = runCheckedSync([installerPath, '--list-apps']);
if (new RegExp(app.id, multiLine: true).hasMatch(apps)) { if (new RegExp(app.id, multiLine: true).hasMatch(apps)) {
return true; return true;
} }
...@@ -190,22 +189,42 @@ class IOSDevice extends Device { ...@@ -190,22 +189,42 @@ class IOSDevice extends Device {
@override @override
Future<bool> startApp(ApplicationPackage app) async { Future<bool> startApp(ApplicationPackage app) async {
if (!isAppInstalled(app)) { logging.fine("Attempting to build and install ${app.name} on $id");
// Step 1: Install the precompiled application if necessary
bool buildResult = await _buildIOSXcodeProject(app, true);
if (!buildResult) {
logging.severe('Could not build the precompiled application for the device');
return false; return false;
} }
// idevicedebug hangs forever after launching the app, so kill it after
// giving it plenty of time to send the launch command. // Step 2: Check that the application exists at the specified path
return await runAndKill( Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphoneos', 'Runner.app'));
[debuggerPath, 'run', app.id],
new Duration(seconds: 3) bool bundleExists = await bundle.exists();
).then( if (!bundleExists) {
(_) { logging.severe('Could not find the built application bundle at ${bundle.path}');
return true; return false;
}, onError: (e) { }
logging.info('Failure running $debuggerPath: ', e);
return false; // Step 3: Attempt to install the application on the device
} int installationResult = await runCommandAndStreamOutput([
); '/usr/bin/env',
'ios-deploy',
'--id',
id,
'--bundle',
bundle.path,
]);
if (installationResult != 0) {
logging.severe('Could not install ${bundle.path} on $id');
return false;
}
logging.fine('Installation successful');
return true;
} }
@override @override
...@@ -230,11 +249,6 @@ class IOSDevice extends Device { ...@@ -230,11 +249,6 @@ class IOSDevice extends Device {
]); ]);
return true; return true;
} else { } else {
// TODO(iansf): It may be possible to make this work on Linux. Since this
// functionality appears to be the only that prevents us from
// supporting iOS on Linux, it may be worth putting some time
// into investigating this.
// See https://bbs.archlinux.org/viewtopic.php?id=192655
return false; return false;
} }
return false; return false;
...@@ -337,8 +351,6 @@ class IOSSimulator extends Device { ...@@ -337,8 +351,6 @@ class IOSSimulator extends Device {
List<IOSSimulator> devices = []; List<IOSSimulator> devices = [];
String id = _getRunningSimulatorID(mockIOS); String id = _getRunningSimulatorID(mockIOS);
if (id != null) { if (id != null) {
// TODO(iansf): get the simulator's name
// String name = _getDeviceName(id, mockIOS);
devices.add(new IOSSimulator(id: id)); devices.add(new IOSSimulator(id: id));
} }
return devices; return devices;
...@@ -421,20 +433,53 @@ class IOSSimulator extends Device { ...@@ -421,20 +433,53 @@ class IOSSimulator extends Device {
@override @override
Future<bool> startApp(ApplicationPackage app) async { Future<bool> startApp(ApplicationPackage app) async {
if (!isAppInstalled(app)) { logging.fine('Building ${app.name} for $id');
// Step 1: Build the Xcode project
bool buildResult = await _buildIOSXcodeProject(app, false);
if (!buildResult) {
logging.severe('Could not build the application for the simulator');
return false; return false;
} }
try {
if (id == defaultDeviceID) { // Step 2: Assert that the Xcode project was successfully built
runCheckedSync( Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphonesimulator', 'Runner.app'));
[xcrunPath, 'simctl', 'launch', 'booted', app.id]); bool bundleExists = await bundle.exists();
} else { if (!bundleExists) {
runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.id]); logging.severe('Could not find the built application bundle at ${bundle.path}');
}
return true;
} catch (e) {
return false; return false;
} }
// Step 3: Install the updated bundle to the simulator
int installResult = await runCommandAndStreamOutput([
xcrunPath,
'simctl',
'install',
id == defaultDeviceID ? 'booted' : id,
path.absolute(bundle.path)
]);
if (installResult != 0) {
logging.severe('Could not install the application bundle on the simulator');
return false;
}
// Step 4: Launch the updated application in the simulator
int launchResult = await runCommandAndStreamOutput([
xcrunPath,
'simctl',
'launch',
id == defaultDeviceID ? 'booted' : id,
app.id
]);
if (launchResult != 0) {
logging.severe('Could not launch the freshly installed application on the simulator');
return false;
}
logging.fine('Successfully started ${app.name} on $id');
return true;
} }
@override @override
...@@ -973,6 +1018,30 @@ class DeviceStore { ...@@ -973,6 +1018,30 @@ class DeviceStore {
this.iOSSimulator this.iOSSimulator
}); });
static Device _deviceForConfig(BuildConfiguration config, List<Device> devices) {
Device device = null;
if (config.deviceId != null) {
// Step 1: If a device identifier is specified, try to find a device
// matching that specific identifier
device = devices.firstWhere(
(Device dev) => (dev.id == config.deviceId),
orElse: () => null);
if (device == null) {
logging.severe('Warning: Device ID ${config.deviceId} not found');
}
} else if (devices.length == 1) {
// Step 2: If no identifier is specified and there is only one connected
// device, pick that one.
device = devices[0];
} else if (devices.length > 1) {
// Step 3: D:
logging.severe('Warning: Multiple devices are connected, but no device ID was specified.');
}
return device;
}
factory DeviceStore.forConfigs(List<BuildConfiguration> configs) { factory DeviceStore.forConfigs(List<BuildConfiguration> configs) {
AndroidDevice android; AndroidDevice android;
IOSDevice iOS; IOSDevice iOS;
...@@ -982,27 +1051,19 @@ class DeviceStore { ...@@ -982,27 +1051,19 @@ class DeviceStore {
switch (config.targetPlatform) { switch (config.targetPlatform) {
case TargetPlatform.android: case TargetPlatform.android:
assert(android == null); assert(android == null);
List<AndroidDevice> androidDevices = AndroidDevice.getAttachedDevices(); android = _deviceForConfig(config, AndroidDevice.getAttachedDevices());
if (config.deviceId != null) {
android = androidDevices.firstWhere(
(AndroidDevice dev) => (dev.id == config.deviceId),
orElse: () => null);
if (android == null) {
print('Warning: Device ID ${config.deviceId} not found');
}
} else if (androidDevices.length == 1) {
android = androidDevices[0];
} else if (androidDevices.length > 1) {
print('Warning: Multiple Android devices are connected, but no device ID was specified.');
}
break; break;
case TargetPlatform.iOS: case TargetPlatform.iOS:
assert(iOS == null); assert(iOS == null);
iOS = new IOSDevice(); iOS = _deviceForConfig(config, IOSDevice.getAttachedDevices());
break; break;
case TargetPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null); assert(iOSSimulator == null);
iOSSimulator = new IOSSimulator(); iOSSimulator = _deviceForConfig(config, IOSSimulator.getAttachedDevices());
if (iOSSimulator == null) {
// Creates a simulator with the default identifier
iOSSimulator = new IOSSimulator();
}
break; break;
case TargetPlatform.mac: case TargetPlatform.mac:
case TargetPlatform.linux: case TargetPlatform.linux:
...@@ -1013,3 +1074,17 @@ class DeviceStore { ...@@ -1013,3 +1074,17 @@ class DeviceStore {
return new DeviceStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator); return new DeviceStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
} }
} }
Future<bool> _buildIOSXcodeProject(ApplicationPackage app, bool isDevice) async {
List<String> command = [
'/usr/bin/env', 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release'
];
if (!isDevice) {
command.addAll(['-sdk', 'iphonesimulator']);
}
int result = await runCommandAndStreamOutput(command,
workingDirectory: app.localPath);
return result == 0;
}
...@@ -226,6 +226,18 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -226,6 +226,18 @@ class FlutterCommandRunner extends CommandRunner {
testable: true testable: true
)); ));
} }
if (hostPlatform == HostPlatform.mac) {
configs.add(new BuildConfiguration.prebuilt(
hostPlatform: HostPlatform.mac,
targetPlatform: TargetPlatform.iOS
));
configs.add(new BuildConfiguration.prebuilt(
hostPlatform: HostPlatform.mac,
targetPlatform: TargetPlatform.iOSSimulator
));
}
} else { } else {
if (!FileSystemEntity.isDirectorySync(enginePath)) if (!FileSystemEntity.isDirectorySync(enginePath))
logging.warning('$enginePath is not a valid directory'); logging.warning('$enginePath is not a valid directory');
......
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