Commit 94b472ff authored by Adam Barth's avatar Adam Barth

Add a --no-http flag to start command

This flag builds a local FLX file and pushes that to the device instead of
using an HTTP server.
parent 48c3d015
...@@ -66,16 +66,15 @@ class ApplicationPackageStore { ...@@ -66,16 +66,15 @@ class ApplicationPackageStore {
ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator }); ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator });
ApplicationPackage getPackageForPlatform(BuildPlatform platform) { ApplicationPackage getPackageForPlatform(TargetPlatform platform) {
switch (platform) { switch (platform) {
case BuildPlatform.android: case TargetPlatform.android:
return android; return android;
case BuildPlatform.iOS: case TargetPlatform.iOS:
return iOS; return iOS;
case BuildPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
return iOSSimulator; return iOSSimulator;
case BuildPlatform.mac: case TargetPlatform.linux:
case BuildPlatform.linux:
return null; return null;
} }
} }
...@@ -86,31 +85,32 @@ class ApplicationPackageStore { ...@@ -86,31 +85,32 @@ class ApplicationPackageStore {
IOSApp iOSSimulator; IOSApp iOSSimulator;
for (BuildConfiguration config in configs) { for (BuildConfiguration config in configs) {
switch (config.platform) { switch (config.targetPlatform) {
case BuildPlatform.android: case TargetPlatform.android:
assert(android == null); assert(android == null);
String localPath = config.type == BuildType.prebuilt ? if (config.type != BuildType.prebuilt) {
await ArtifactStore.getPath(Artifact.flutterShell) : String localPath = path.join(config.buildDir, 'apks', AndroidApk._defaultName);
path.join(config.buildDir, 'apks', AndroidApk._defaultName); android = new AndroidApk(localPath: localPath);
android = new AndroidApk(localPath: localPath); } else {
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.shell, targetPlatform: TargetPlatform.android);
android = new AndroidApk(localPath: await ArtifactStore.getPath(artifact));
}
break; break;
case BuildPlatform.iOS: case TargetPlatform.iOS:
assert(iOS == null); assert(iOS == null);
assert(config.type != BuildType.prebuilt); assert(config.type != BuildType.prebuilt);
iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName)); iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
case BuildPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null); assert(iOSSimulator == null);
assert(config.type != BuildType.prebuilt); assert(config.type != BuildType.prebuilt);
iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName)); iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
case BuildPlatform.mac: case TargetPlatform.linux:
case BuildPlatform.linux:
// TODO(abarth): Support mac and linux targets.
assert(false);
break; break;
} }
} }
......
...@@ -10,15 +10,147 @@ import 'dart:io'; ...@@ -10,15 +10,147 @@ import 'dart:io';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'build_configuration.dart';
final Logger _logging = new Logger('sky_tools.artifacts'); final Logger _logging = new Logger('sky_tools.artifacts');
enum Artifact { const String _kShellCategory = 'shell';
flutterCompiler, const String _kViewerCategory = 'viewer';
flutterShell,
skyViewerMojo, String _getNameForHostPlatform(HostPlatform platform) {
switch (platform) {
case HostPlatform.linux:
return 'linux-x64';
case HostPlatform.mac:
return 'darwin-x64';
}
}
String _getNameForTargetPlatform(TargetPlatform platform) {
switch (platform) {
case TargetPlatform.android:
return 'android-arm';
case TargetPlatform.iOS:
return 'ios-arm';
case TargetPlatform.iOSSimulator:
return 'ios-x64';
case TargetPlatform.linux:
return 'linux-x64';
}
}
// Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/big_red_button.py#L50
String _getCloudStorageBaseUrl({String category, String platform, String revision}) {
if (platform == 'darwin-x64') {
// In the fullness of time, we'll have a consistent URL pattern for all of
// our artifacts, but, for the time being, darwin artifacts are stored in a
// different cloud storage bucket.
return 'https://storage.googleapis.com/mojo_infra/flutter/${platform}/${revision}/';
}
return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${revision}/';
}
enum ArtifactType {
snapshot,
shell,
viewer,
}
class Artifact {
const Artifact._({
this.name,
this.fileName,
this.category,
this.type,
this.hostPlatform,
this.targetPlatform
});
final String name;
final String fileName;
final String category; // TODO(abarth): Remove categories.
final ArtifactType type;
final HostPlatform hostPlatform;
final TargetPlatform targetPlatform;
String get platform {
if (targetPlatform != null)
return _getNameForTargetPlatform(targetPlatform);
if (hostPlatform != null)
return _getNameForHostPlatform(hostPlatform);
assert(false);
return null;
}
String getUrl(String revision) {
return _getCloudStorageBaseUrl(category: category, platform: platform, revision: revision) + fileName;
}
// Whether the artifact needs to be marked as executable on disk.
bool get executable => type == ArtifactType.snapshot;
} }
class ArtifactStore { class ArtifactStore {
static const List<Artifact> knownArtifacts = const <Artifact>[
const Artifact._(
name: 'Sky Shell',
fileName: 'SkyShell.apk',
category: _kShellCategory,
type: ArtifactType.shell,
targetPlatform: TargetPlatform.android
),
const Artifact._(
name: 'Sky Snapshot',
fileName: 'sky_snapshot',
category: _kShellCategory,
type: ArtifactType.snapshot,
hostPlatform: HostPlatform.linux
),
const Artifact._(
name: 'Sky Snapshot',
fileName: 'sky_snapshot',
category: _kShellCategory,
type: ArtifactType.snapshot,
hostPlatform: HostPlatform.mac
),
const Artifact._(
name: 'Sky Viewer',
fileName: 'sky_viewer.mojo',
category: _kViewerCategory,
type: ArtifactType.viewer,
targetPlatform: TargetPlatform.android
),
const Artifact._(
name: 'Sky Viewer',
fileName: 'sky_viewer.mojo',
category: _kViewerCategory,
type: ArtifactType.viewer,
targetPlatform: TargetPlatform.linux
),
];
static Artifact getArtifact({
ArtifactType type,
HostPlatform hostPlatform,
TargetPlatform targetPlatform
}) {
for (Artifact artifact in ArtifactStore.knownArtifacts) {
if (type != null &&
type != artifact.type)
continue;
if (hostPlatform != null &&
artifact.hostPlatform != null &&
hostPlatform != artifact.hostPlatform)
continue;
if (targetPlatform != null &&
artifact.targetPlatform != null &&
targetPlatform != artifact.targetPlatform)
continue;
return artifact;
}
return null;
}
static String packageRoot; static String packageRoot;
static String _engineRevision; static String _engineRevision;
...@@ -31,102 +163,68 @@ class ArtifactStore { ...@@ -31,102 +163,68 @@ class ArtifactStore {
return _engineRevision; return _engineRevision;
} }
// Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/big_red_button.py#L50 static String getCloudStorageBaseUrl(String category, String platform) {
static String googleStorageUrl(String category, String platform) { return _getCloudStorageBaseUrl(category: category, platform: platform, revision: engineRevision);
return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${engineRevision}/';
} }
static Future _downloadFile(String url, File file) async { static Future _downloadFile(String url, File file) async {
print('Downloading $url to ${file.path}.'); _logging.info('Downloading $url to ${file.path}.');
HttpClient httpClient = new HttpClient(); HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse(url)); HttpClientRequest request = await httpClient.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close(); HttpClientResponse response = await request.close();
_logging.fine('Received response'); _logging.fine('Received response statusCode=${response.statusCode}');
if (response.statusCode != 200) throw new Exception(response.reasonPhrase); if (response.statusCode != 200)
throw new Exception(response.reasonPhrase);
IOSink sink = file.openWrite(); IOSink sink = file.openWrite();
await sink.addStream(response); await sink.addStream(response);
await sink.close(); await sink.close();
_logging.fine('Wrote file'); _logging.fine('Wrote file');
} }
static Future<Directory> _cacheDir() async { static Directory _getBaseCacheDir() {
Directory cacheDir = new Directory(path.join(packageRoot, 'sky_tools', 'cache')); Directory cacheDir = new Directory(path.join(packageRoot, 'sky_tools', 'cache'));
if (!await cacheDir.exists()) { if (!cacheDir.existsSync())
await cacheDir.create(recursive: true); cacheDir.createSync(recursive: true);
}
return cacheDir; return cacheDir;
} }
static Future<Directory> _engineSpecificCacheDir() async { static Directory _getCacheDirForArtifact(Artifact artifact) {
Directory cacheDir = await _cacheDir(); Directory baseDir = _getBaseCacheDir();
// For now, all downloaded artifacts are release mode host binaries so use // For now, all downloaded artifacts are release mode host binaries so use
// a path that mirrors a local release build. // a path that mirrors a local release build.
// TODO(jamesr): Add support for more configurations. // TODO(jamesr): Add support for more configurations.
String config = 'Release'; String config = 'Release';
Directory engineSpecificDir = new Directory(path.join(cacheDir.path, 'sky_engine', engineRevision, config)); Directory artifactSpecificDir = new Directory(path.join(
baseDir.path, 'sky_engine', engineRevision, config, artifact.platform));
if (!await engineSpecificDir.exists()) { if (!artifactSpecificDir.existsSync())
await engineSpecificDir.create(recursive: true); artifactSpecificDir.createSync(recursive: true);
} return artifactSpecificDir;
return engineSpecificDir;
}
// Whether the artifact needs to be marked as executable on disk.
static bool _needsToBeExecutable(Artifact artifact) {
return artifact == Artifact.flutterCompiler;
} }
static Future<String> getPath(Artifact artifact) async { static Future<String> getPath(Artifact artifact) async {
Directory cacheDir = await _engineSpecificCacheDir(); Directory cacheDir = _getCacheDirForArtifact(artifact);
File cachedFile = new File(path.join(cacheDir.path, artifact.fileName));
String category; if (!cachedFile.existsSync()) {
String platform; print('Downloading ${artifact.name} from the cloud, one moment please...');
String name; await _downloadFile(artifact.getUrl(engineRevision), cachedFile);
if (artifact.executable) {
switch (artifact) { // TODO(abarth): We should factor this out into a separate function that
case Artifact.flutterCompiler: // can have a platform-specific implementation.
category = 'shell'; ProcessResult result = Process.runSync('chmod', ['u+x', cachedFile.path]);
name = 'sky_snapshot'; if (result.exitCode != 0)
break; throw new Exception(result.stderr);
case Artifact.flutterShell:
category = 'shell';
platform = 'android-arm';
name = 'SkyShell.apk';
break;
case Artifact.skyViewerMojo:
category = 'viewer';
name = 'sky_viewer.mojo';
break;
}
File cachedFile = new File(path.join(cacheDir.path, name));
if (!await cachedFile.exists()) {
_logging.info('Downloading ${name} from the cloud, one moment please...');
if (platform == null) {
if (!Platform.isLinux)
throw new Exception('Platform unsupported.');
platform = 'linux-x64';
}
String url = googleStorageUrl(category, platform) + name;
await _downloadFile(url, cachedFile);
if (_needsToBeExecutable(artifact)) {
ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]);
if (result.exitCode != 0) throw new Exception(result.stderr);
} }
} }
return cachedFile.path; return cachedFile.path;
} }
static Future clear() async { static void clear() {
Directory cacheDir = await _cacheDir(); Directory cacheDir = _getBaseCacheDir();
_logging.fine('Clearing cache directory ${cacheDir.path}'); _logging.fine('Clearing cache directory ${cacheDir.path}');
await cacheDir.delete(recursive: true); cacheDir.deleteSync(recursive: true);
} }
static Future populate() async { static Future populate() {
for (Artifact artifact in Artifact.values) { return Future.wait(knownArtifacts.map((artifact) => getPath(artifact)));
_logging.fine('Populating cache with $artifact');
await getPath(artifact);
}
} }
} }
...@@ -2,29 +2,48 @@ ...@@ -2,29 +2,48 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
final Logger _logging = new Logger('sky_tools.build_configuration');
enum BuildType { enum BuildType {
prebuilt, prebuilt,
release, release,
debug, debug,
} }
enum BuildPlatform { enum HostPlatform {
mac,
linux,
}
enum TargetPlatform {
android, android,
iOS, iOS,
iOSSimulator, iOSSimulator,
mac,
linux, linux,
} }
HostPlatform getCurrentHostPlatform() {
if (Platform.isMacOS)
return HostPlatform.mac;
if (Platform.isLinux)
return HostPlatform.linux;
_logging.warning('Unsupported host platform, defaulting to Linux');
return HostPlatform.linux;
}
class BuildConfiguration { class BuildConfiguration {
BuildConfiguration.prebuilt({ this.platform }) BuildConfiguration.prebuilt({ this.hostPlatform, this.targetPlatform })
: type = BuildType.prebuilt, buildDir = null; : type = BuildType.prebuilt, buildDir = null;
BuildConfiguration.local({ BuildConfiguration.local({
this.type, this.type,
this.platform, this.hostPlatform,
this.targetPlatform,
String enginePath, String enginePath,
String buildPath String buildPath
}) : buildDir = path.normalize(path.join(enginePath, buildPath)) { }) : buildDir = path.normalize(path.join(enginePath, buildPath)) {
...@@ -32,6 +51,7 @@ class BuildConfiguration { ...@@ -32,6 +51,7 @@ class BuildConfiguration {
} }
final BuildType type; final BuildType type;
final BuildPlatform platform; final HostPlatform hostPlatform;
final TargetPlatform targetPlatform;
final String buildDir; final String buildDir;
} }
...@@ -27,8 +27,10 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -27,8 +27,10 @@ class FlutterCommandRunner extends CommandRunner {
negatable: false, negatable: false,
help: 'Very noisy logging, including the output of all ' help: 'Very noisy logging, including the output of all '
'shell commands executed.'); 'shell commands executed.');
argParser.addOption('package-root',
help: 'Path to your packages directory.', defaultsTo: 'packages');
argParser.addSeparator('Global build selection options:'); argParser.addSeparator('Local build selection options:');
argParser.addFlag('debug', argParser.addFlag('debug',
negatable: false, negatable: false,
help: help:
...@@ -48,42 +50,40 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -48,42 +50,40 @@ class FlutterCommandRunner extends CommandRunner {
'Automatically detect your engine src directory from an overridden Flutter package.' 'Automatically detect your engine src directory from an overridden Flutter package.'
'Useful if you are building Flutter locally and are using a dependency_override for' 'Useful if you are building Flutter locally and are using a dependency_override for'
'the Flutter package that points to your engine src directory.'); 'the Flutter package that points to your engine src directory.');
argParser.addOption('engine-src-path', argParser.addOption('engine-src-path', hide: true,
help: help:
'Path to your engine src directory, if you are building Flutter locally. ' 'Path to your engine src directory, if you are building Flutter locally. '
'Ignored if neither debug nor release is set. Not normally required.'); 'Ignored if neither debug nor release is set. Not normally required.');
argParser.addOption('android-debug-build-path', argParser.addOption('android-debug-build-path', hide: true,
help: help:
'Path to your Android Debug out directory, if you are building Flutter locally. ' 'Path to your Android Debug out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/android_Debug/'); defaultsTo: 'out/android_Debug/');
argParser.addOption('android-release-build-path', argParser.addOption('android-release-build-path', hide: true,
help: help:
'Path to your Android Release out directory, if you are building Flutter locally. ' 'Path to your Android Release out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/android_Release/'); defaultsTo: 'out/android_Release/');
argParser.addOption('ios-debug-build-path', argParser.addOption('ios-debug-build-path', hide: true,
help: help:
'Path to your iOS Debug out directory, if you are building Flutter locally. ' 'Path to your iOS Debug out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_Debug/'); defaultsTo: 'out/ios_Debug/');
argParser.addOption('ios-release-build-path', argParser.addOption('ios-release-build-path', hide: true,
help: help:
'Path to your iOS Release out directory, if you are building Flutter locally. ' 'Path to your iOS Release out directory, if you are building Flutter locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_Release/'); defaultsTo: 'out/ios_Release/');
argParser.addOption('ios-sim-debug-build-path', argParser.addOption('ios-sim-debug-build-path', hide: true,
help: help:
'Path to your iOS Simulator Debug out directory, if you are building Sky locally. ' 'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Debug/'); defaultsTo: 'out/ios_sim_Debug/');
argParser.addOption('ios-sim-release-build-path', argParser.addOption('ios-sim-release-build-path', hide: true,
help: help:
'Path to your iOS Simulator Release out directory, if you are building Sky locally. ' 'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
'This path is relative to engine-src-path. Not normally required.', 'This path is relative to engine-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Release/'); defaultsTo: 'out/ios_sim_Release/');
argParser.addOption('package-root',
help: 'Path to your packages directory.', defaultsTo: 'packages');
} }
List<BuildConfiguration> get buildConfigurations { List<BuildConfiguration> get buildConfigurations {
...@@ -124,6 +124,7 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -124,6 +124,7 @@ class FlutterCommandRunner extends CommandRunner {
String enginePath = globalResults['engine-src-path']; String enginePath = globalResults['engine-src-path'];
bool isDebug = globalResults['debug']; bool isDebug = globalResults['debug'];
bool isRelease = globalResults['release']; bool isRelease = globalResults['release'];
HostPlatform hostPlatform = getCurrentHostPlatform();
if (enginePath == null && globalResults['local-build']) { if (enginePath == null && globalResults['local-build']) {
Directory flutterDir = new Directory(path.join(globalResults['package-root'], 'flutter')); Directory flutterDir = new Directory(path.join(globalResults['package-root'], 'flutter'));
...@@ -141,7 +142,8 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -141,7 +142,8 @@ class FlutterCommandRunner extends CommandRunner {
List<BuildConfiguration> configs = <BuildConfiguration>[]; List<BuildConfiguration> configs = <BuildConfiguration>[];
if (enginePath == null) { if (enginePath == null) {
configs.add(new BuildConfiguration.prebuilt(platform: BuildPlatform.android)); configs.add(new BuildConfiguration.prebuilt(
hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android));
} 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');
...@@ -152,7 +154,8 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -152,7 +154,8 @@ class FlutterCommandRunner extends CommandRunner {
if (isDebug) { if (isDebug) {
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.debug, type: BuildType.debug,
platform: BuildPlatform.android, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.android,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['android-debug-build-path'] buildPath: globalResults['android-debug-build-path']
)); ));
...@@ -160,14 +163,16 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -160,14 +163,16 @@ class FlutterCommandRunner extends CommandRunner {
if (Platform.isMacOS) { if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.debug, type: BuildType.debug,
platform: BuildPlatform.iOS, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOS,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['ios-debug-build-path'] buildPath: globalResults['ios-debug-build-path']
)); ));
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.debug, type: BuildType.debug,
platform: BuildPlatform.iOSSimulator, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOSSimulator,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['ios-sim-debug-build-path'] buildPath: globalResults['ios-sim-debug-build-path']
)); ));
...@@ -177,7 +182,8 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -177,7 +182,8 @@ class FlutterCommandRunner extends CommandRunner {
if (isRelease) { if (isRelease) {
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.release, type: BuildType.release,
platform: BuildPlatform.android, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.android,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['android-release-build-path'] buildPath: globalResults['android-release-build-path']
)); ));
...@@ -185,14 +191,16 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -185,14 +191,16 @@ class FlutterCommandRunner extends CommandRunner {
if (Platform.isMacOS) { if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.release, type: BuildType.release,
platform: BuildPlatform.iOS, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOS,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['ios-release-build-path'] buildPath: globalResults['ios-release-build-path']
)); ));
configs.add(new BuildConfiguration.local( configs.add(new BuildConfiguration.local(
type: BuildType.release, type: BuildType.release,
platform: BuildPlatform.iOSSimulator, hostPlatform: hostPlatform,
targetPlatform: TargetPlatform.iOSSimulator,
enginePath: enginePath, enginePath: enginePath,
buildPath: globalResults['ios-sim-release-build-path'] buildPath: globalResults['ios-sim-release-build-path']
)); ));
......
...@@ -9,6 +9,7 @@ import 'package:args/command_runner.dart'; ...@@ -9,6 +9,7 @@ import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../build_configuration.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../process.dart'; import '../process.dart';
...@@ -40,7 +41,7 @@ class RunMojoCommand extends Command { ...@@ -40,7 +41,7 @@ class RunMojoCommand extends Command {
} }
Future<int> _runAndroid(String devtoolsPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) { Future<int> _runAndroid(String devtoolsPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) {
String skyViewerUrl = ArtifactStore.googleStorageUrl('viewer', 'android-arm'); String skyViewerUrl = ArtifactStore.getCloudStorageBaseUrl('viewer', 'android-arm');
String command = _makePathAbsolute(devtoolsPath); String command = _makePathAbsolute(devtoolsPath);
String appName = path.basename(appPath); String appName = path.basename(appPath);
String appDir = path.dirname(appPath); String appDir = path.dirname(appPath);
...@@ -65,7 +66,8 @@ class RunMojoCommand extends Command { ...@@ -65,7 +66,8 @@ class RunMojoCommand extends Command {
} }
Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async { Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async {
String viewerPath = _makePathAbsolute(await ArtifactStore.getPath(Artifact.skyViewerMojo)); Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.viewer, targetPlatform: TargetPlatform.linux);
String viewerPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release'; String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release';
String mojoShellPath = _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell')); String mojoShellPath = _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell'));
List<String> cmd = [ List<String> cmd = [
......
...@@ -3,17 +3,21 @@ ...@@ -3,17 +3,21 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import 'build.dart';
import 'flutter_command.dart'; import 'flutter_command.dart';
import 'install.dart'; import 'install.dart';
import 'stop.dart'; import 'stop.dart';
final Logger _logging = new Logger('sky_tools.start'); final Logger _logging = new Logger('sky_tools.start');
const String _localBundlePath = 'app.flx';
const bool _kUseServer = true;
class StartCommand extends FlutterCommand { class StartCommand extends FlutterCommand {
final String name = 'start'; final String name = 'start';
...@@ -31,13 +35,20 @@ class StartCommand extends FlutterCommand { ...@@ -31,13 +35,20 @@ class StartCommand extends FlutterCommand {
defaultsTo: '.', defaultsTo: '.',
abbr: 't', abbr: 't',
help: 'Target app path or filename to start.'); help: 'Target app path or filename to start.');
argParser.addFlag('http',
negatable: true,
defaultsTo: true,
help: 'Use a local HTTP server to serve your app to your device.');
argParser.addFlag('boot', argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.'); help: 'Boot the iOS Simulator if it isn\'t already running.');
} }
@override @override
Future<int> runInProject() async { Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices(); await Future.wait([
downloadToolchain(),
downloadApplicationPackagesAndConnectToDevices(),
]);
bool poke = argResults['poke']; bool poke = argResults['poke'];
if (!poke) { if (!poke) {
...@@ -59,8 +70,19 @@ class StartCommand extends FlutterCommand { ...@@ -59,8 +70,19 @@ class StartCommand extends FlutterCommand {
continue; continue;
if (device is AndroidDevice) { if (device is AndroidDevice) {
String target = path.absolute(argResults['target']); String target = path.absolute(argResults['target']);
if (await device.startServer(target, poke, argResults['checked'], package)) if (argResults['http']) {
startedSomething = true; if (await device.startServer(target, poke, argResults['checked'], package))
startedSomething = true;
} else {
String mainPath = target;
if (FileSystemEntity.isDirectorySync(target))
mainPath = path.join(target, 'lib', 'main.dart');
BuildCommand builder = new BuildCommand();
builder.inheritFromParent(this);
await builder.build(outputPath: _localBundlePath, mainPath: mainPath);
if (device.startBundle(package, _localBundlePath, poke, argResults['checked']))
startedSomething = true;
}
} else { } else {
if (await device.startApp(package)) if (await device.startApp(package))
startedSomething = true; startedSomething = true;
......
...@@ -66,7 +66,7 @@ abstract class Device { ...@@ -66,7 +66,7 @@ abstract class Device {
/// Check if the current version of the given app is already installed /// Check if the current version of the given app is already installed
bool isAppInstalled(ApplicationPackage app); bool isAppInstalled(ApplicationPackage app);
BuildPlatform get platform; TargetPlatform get platform;
Future<int> logs({bool clear: false}); Future<int> logs({bool clear: false});
...@@ -271,7 +271,7 @@ class IOSDevice extends Device { ...@@ -271,7 +271,7 @@ class IOSDevice extends Device {
} }
@override @override
BuildPlatform get platform => BuildPlatform.iOS; TargetPlatform get platform => TargetPlatform.iOS;
/// Note that clear is not supported on iOS at this time. /// Note that clear is not supported on iOS at this time.
Future<int> logs({bool clear: false}) async { Future<int> logs({bool clear: false}) async {
...@@ -487,7 +487,7 @@ class IOSSimulator extends Device { ...@@ -487,7 +487,7 @@ class IOSSimulator extends Device {
} }
@override @override
BuildPlatform get platform => BuildPlatform.iOSSimulator; TargetPlatform get platform => TargetPlatform.iOSSimulator;
Future<int> logs({bool clear: false}) async { Future<int> logs({bool clear: false}) async {
if (!isConnected()) { if (!isConnected()) {
...@@ -695,6 +695,10 @@ class AndroidDevice extends Device { ...@@ -695,6 +695,10 @@ class AndroidDevice extends Device {
return '${_getDeviceDataPath(app)}/${app.name}.sha1'; return '${_getDeviceDataPath(app)}/${app.name}.sha1';
} }
String _getDeviceBundlePath(ApplicationPackage app) {
return '${_getDeviceDataPath(app)}/dev.flx';
}
String _getDeviceApkSha1(ApplicationPackage app) { String _getDeviceApkSha1(ApplicationPackage app) {
return runCheckedSync([adbPath, 'shell', 'cat', _getDeviceSha1Path(app)]); return runCheckedSync([adbPath, 'shell', 'cat', _getDeviceSha1Path(app)]);
} }
...@@ -750,12 +754,45 @@ class AndroidDevice extends Device { ...@@ -750,12 +754,45 @@ class AndroidDevice extends Device {
return true; return true;
} }
void _forwardObservatoryPort() {
// Set up port forwarding for observatory.
String observatoryPortString = 'tcp:$_observatoryPort';
runCheckedSync(
[adbPath, 'forward', observatoryPortString, observatoryPortString]);
}
bool startBundle(AndroidApk apk, String bundlePath, bool poke, bool checked) {
if (!FileSystemEntity.isFileSync(bundlePath)) {
_logging.severe('Cannot find $bundlePath');
return false;
}
if (!poke)
_forwardObservatoryPort();
String deviceTmpPath = '/data/local/tmp/dev.flx';
String deviceBundlePath = _getDeviceBundlePath(apk);
runCheckedSync([adbPath, 'push', bundlePath, deviceTmpPath]);
runCheckedSync([adbPath, 'shell', 'mv', deviceTmpPath, deviceBundlePath]);
List<String> cmd = [
adbPath,
'shell', 'am', 'start',
'-a', 'android.intent.action.RUN',
'-d', deviceBundlePath,
];
if (checked)
cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
cmd.add(apk.launchActivity);
runCheckedSync(cmd);
return true;
}
Future<bool> startServer( Future<bool> startServer(
String target, bool poke, bool checked, AndroidApk apk) async { String target, bool poke, bool checked, AndroidApk apk) async {
String serverRoot = ''; String serverRoot = '';
String mainDart = ''; String mainDart = '';
String missingMessage = ''; String missingMessage = '';
if (await FileSystemEntity.isDirectory(target)) { if (FileSystemEntity.isDirectorySync(target)) {
serverRoot = target; serverRoot = target;
mainDart = path.join(serverRoot, 'lib', 'main.dart'); mainDart = path.join(serverRoot, 'lib', 'main.dart');
missingMessage = 'Missing lib/main.dart in project: $serverRoot'; missingMessage = 'Missing lib/main.dart in project: $serverRoot';
...@@ -765,16 +802,13 @@ class AndroidDevice extends Device { ...@@ -765,16 +802,13 @@ class AndroidDevice extends Device {
missingMessage = '$mainDart does not exist.'; missingMessage = '$mainDart does not exist.';
} }
if (!await FileSystemEntity.isFile(mainDart)) { if (!FileSystemEntity.isFileSync(mainDart)) {
_logging.severe(missingMessage); _logging.severe(missingMessage);
return false; return false;
} }
if (!poke) { if (!poke) {
// Set up port forwarding for observatory. _forwardObservatoryPort();
String observatoryPortString = 'tcp:$_observatoryPort';
runCheckedSync(
[adbPath, 'forward', observatoryPortString, observatoryPortString]);
// Actually start the server. // Actually start the server.
Process server = await Process.start( Process server = await Process.start(
...@@ -794,28 +828,20 @@ class AndroidDevice extends Device { ...@@ -794,28 +828,20 @@ class AndroidDevice extends Device {
String relativeDartMain = _convertToURL(path.relative(mainDart, from: serverRoot)); String relativeDartMain = _convertToURL(path.relative(mainDart, from: serverRoot));
String url = 'http://localhost:$_serverPort/$relativeDartMain'; String url = 'http://localhost:$_serverPort/$relativeDartMain';
if (poke) { if (poke)
url += '?rand=${new Random().nextDouble()}'; url += '?rand=${new Random().nextDouble()}';
}
// Actually launch the app on Android. // Actually launch the app on Android.
List<String> cmd = [ List<String> cmd = [
adbPath, adbPath,
'shell', 'shell', 'am', 'start',
'am', '-a', 'android.intent.action.VIEW',
'start', '-d', url,
'-a',
'android.intent.action.VIEW',
'-d',
url,
]; ];
if (checked) { if (checked)
cmd.addAll(['--ez', 'enable-checked-mode', 'true']); cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
}
cmd.add(apk.launchActivity); cmd.add(apk.launchActivity);
runCheckedSync(cmd); runCheckedSync(cmd);
return true; return true;
} }
...@@ -880,7 +906,7 @@ class AndroidDevice extends Device { ...@@ -880,7 +906,7 @@ class AndroidDevice extends Device {
} }
@override @override
BuildPlatform get platform => BuildPlatform.android; TargetPlatform get platform => TargetPlatform.android;
void clearLogs() { void clearLogs() {
runSync([adbPath, 'logcat', '-c']); runSync([adbPath, 'logcat', '-c']);
...@@ -985,24 +1011,20 @@ class DeviceStore { ...@@ -985,24 +1011,20 @@ class DeviceStore {
IOSSimulator iOSSimulator; IOSSimulator iOSSimulator;
for (BuildConfiguration config in configs) { for (BuildConfiguration config in configs) {
switch (config.platform) { switch (config.targetPlatform) {
case BuildPlatform.android: case TargetPlatform.android:
assert(android == null); assert(android == null);
android = new AndroidDevice(); android = new AndroidDevice();
break; break;
case BuildPlatform.iOS: case TargetPlatform.iOS:
assert(iOS == null); assert(iOS == null);
iOS = new IOSDevice(); iOS = new IOSDevice();
break; break;
case BuildPlatform.iOSSimulator: case TargetPlatform.iOSSimulator:
assert(iOSSimulator == null); assert(iOSSimulator == null);
iOSSimulator = new IOSSimulator(); iOSSimulator = new IOSSimulator();
break; break;
case TargetPlatform.linux:
case BuildPlatform.mac:
case BuildPlatform.linux:
// TODO(abarth): Support mac and linux targets.
assert(false);
break; break;
} }
} }
......
...@@ -11,16 +11,16 @@ import 'build_configuration.dart'; ...@@ -11,16 +11,16 @@ import 'build_configuration.dart';
import 'process.dart'; import 'process.dart';
class Compiler { class Compiler {
Compiler(this._compilerPath); Compiler(this._path);
String _compilerPath; String _path;
Future<int> compile({ Future<int> compile({
String mainPath, String mainPath,
String snapshotPath String snapshotPath
}) { }) {
return runCommandAndStreamOutput([ return runCommandAndStreamOutput([
_compilerPath, _path,
mainPath, mainPath,
'--package-root=${ArtifactStore.packageRoot}', '--package-root=${ArtifactStore.packageRoot}',
'--snapshot=$snapshotPath' '--snapshot=$snapshotPath'
...@@ -28,18 +28,22 @@ class Compiler { ...@@ -28,18 +28,22 @@ class Compiler {
} }
} }
Future<String> _getCompilerPath(BuildConfiguration config) async {
if (config.type != BuildType.prebuilt)
return path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
Artifact artifact = ArtifactStore.getArtifact(
type: ArtifactType.snapshot, hostPlatform: config.hostPlatform);
return await ArtifactStore.getPath(artifact);
}
class Toolchain { class Toolchain {
Toolchain({ this.compiler }); Toolchain({ this.compiler });
final Compiler compiler; final Compiler compiler;
static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async { static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async {
// TODO(abarth): Add a notion of "host platform" to the build configs. // TODO(abarth): Shouldn't we consider all the configs?
BuildConfiguration config = configs.first; String compilerPath = await _getCompilerPath(configs.first);
String compilerPath = config.type == BuildType.prebuilt ?
await ArtifactStore.getPath(Artifact.flutterCompiler) :
path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
return new Toolchain(compiler: new Compiler(compilerPath)); return new Toolchain(compiler: new Compiler(compilerPath));
} }
} }
...@@ -26,21 +26,21 @@ class MockToolchain extends Toolchain { ...@@ -26,21 +26,21 @@ class MockToolchain extends Toolchain {
} }
class MockAndroidDevice extends Mock implements AndroidDevice { class MockAndroidDevice extends Mock implements AndroidDevice {
BuildPlatform get platform => BuildPlatform.android; TargetPlatform get platform => TargetPlatform.android;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
class MockIOSDevice extends Mock implements IOSDevice { class MockIOSDevice extends Mock implements IOSDevice {
BuildPlatform get platform => BuildPlatform.iOS; TargetPlatform get platform => TargetPlatform.iOS;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
class MockIOSSimulator extends Mock implements IOSSimulator { class MockIOSSimulator extends Mock implements IOSSimulator {
BuildPlatform get platform => BuildPlatform.iOSSimulator; TargetPlatform get platform => TargetPlatform.iOSSimulator;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
......
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